Integration Services - Extending Packages With Scripting
Integration Services - Extending Packages With Scripting
Summary: You can extend the power of Integration Services (SSIS) by adding code within the wrappers provided by the Script task and the Script component. This section of the Developer Reference provides instructions and examples for extending the control flow and data flow of an SSIS package using the Script task and the Script component. Category: Reference Applies to: SQL Server 2012 Source: SQL Server 2012 Books Online (link to source content) E-book publication date: January 2013
Copyright 2012 by Microsoft Corporation All rights reserved. No part of the contents of this book may be reproduced or transmitted in any form or by any means without the written permission of the publisher.
Microsoft and the trademarks listed at http://www.microsoft.com/about/legal/en/us/IntellectualProperty/Trademarks/EN-US.aspx are trademarks of the Microsoft group of companies. All other marks are property of their respective owners. The example companies, organizations, products, domain names, email addresses, logos, people, places, and events depicted herein are fictitious. No association with any real company, organization, product, domain name, email address, logo, person, place, or event is intended or should be inferred. This book expresses the authors views and opinions. The information contained in this book is provided without any express, statutory, or implied warranties. Neither the authors, Microsoft Corporation, nor its resellers, or distributors will be held liable for any damages caused or alleged to be caused either directly or indirectly by this book.
Contents
Extending Packages with Scripting ........................................................................................................... 3 Comparing the Script Task and the Script Component .................................................................... 5 Comparing Scripting Solutions and Custom Objects ........................................................................ 9 Referencing Other Assemblies in Scripting Solutions ..................................................................... 10 Debug a Script by Setting Breakpoints in a Script Task and Script Component ................... 12 Extending the Package with the Script Task........................................................................................ 13 Configuring the Script Task in the Script Task Editor .................................................................. 15 Coding and Debugging the Script Task ............................................................................................ 17 Using Variables in the Script Task ....................................................................................................... 25 Connecting to Data Sources in the Script Task .............................................................................. 29 Raising Events in the Script Task.......................................................................................................... 33 Logging in the Script Task...................................................................................................................... 37 Returning Results from the Script Task ............................................................................................. 40 Script Task Examples ................................................................................................................................ 41 Detecting an Empty Flat File with the Script Task ..................................................................... 43 Gathering a List for the ForEach Loop with the Script Task................................................... 47 Querying the Active Directory with the Script Task .................................................................. 55 Monitoring Performance Counters with the Script Task......................................................... 58 Working with Images with the Script Task ................................................................................... 62 Finding Installed Printers with the Script Task ............................................................................ 70 Sending an HTML Mail Message with the Script Task............................................................. 74 Working with Excel Files with the Script Task ............................................................................. 78 Sending to a Remote Private Message Queue with the Script Task .................................. 92 Extending the Data Flow with the Script Component ..................................................................... 95 Configuring the Script Component in the Script Component Editor .................................... 97 Coding and Debugging the Script Component .......................................................................... 102 Understanding the Script Component Object Model............................................................... 109 Using Variables in the Script Component ..................................................................................... 115 Connecting to Data Sources in the Script Component ............................................................ 117 Raising Events in the Script Component........................................................................................ 119 Logging in the Script Component.................................................................................................... 122 Developing Specific Types of Script Components ..................................................................... 123 Creating a Source with the Script Component ........................................................................ 124 Creating a Synchronous Transformation with the Script Component............................ 136 Creating an Asynchronous Transformation with the Script Component....................... 145 Creating a Destination with the Script Component............................................................... 153 Additional Script Component Examples ........................................................................................ 164
Simulating an Error Output for the Script Component ........................................................ 165 Enhancing an Error Output with the Script Component ...................................................... 168 Creating an ODBC Destination with the Script Component............................................... 171 Parsing Non-Standard Text File Formats with the Script Component ........................... 176
In This Section
Comparing the Script Task and the Script Component
Discusses the similarities and differences between the Script task and the Script component.
Reference
Integration Services Error Reference
Lists the predefined Integration Services error codes with their symbolic names and descriptions.
Related Sections
Extending Packages with Custom Objects
Discusses how to create program custom tasks, data flow components, and other package objects for use in multiple packages.
Stay Up to Date with Integration Services For the latest downloads, articles, samples, and videos from Microsoft, as well as selected solutions from the community, visit the Integration Services page on MSDN:
See Also
SQL Server Integration Services (SSIS)
In both the task and the component, you begin by specifying properties in the editor, and then switch to the development environment to write code. Both the task and the component use the same VSTA IDE, and support code written in either Microsoft Visual Basic or Microsoft Visual C#. Beginning in SQL Server 2008 Integration Services (SSIS), all scripts are precompiled. In earlier versions, you could specify whether scripts were precompiled. The script is precompiled into binary code, permitting faster execution, but at the cost of increased package size.
Precompiled scripts
Debugging
Both the task and the component support breakpoints and stepping through code while debugging in the design environment. For more information, see Coding and Debugging the Script Task and Coding and Debugging the Script Component. 5
The Script task is configured on the Control Flow tab of the designer and runs outside the data flow of the package. A Script task can accomplish almost any general-purpose task. A Script task runs custom code at some point in the package workflow. Unless you put it in a loop container or an event handler, it only runs once. The Script Task Editor has three pages: General, Script, and Expressions. Only the ReadOnlyVariables and ReadWriteVariables, and ScriptLanguage properties directly affect the code that you can write. In the code written for a Script task, you use the Dts property to access other features of the package. The Dts property is a member of the ScriptMain class.
The Script component is configured on the Data Flow page of the designer and represents a source, transformation, or destination in the Data Flow task. You must specify whether you want to create a source, transformation, or destination with the Script component. A Script component also runs once, but typically it runs its main processing routine once for each row of data in the data flow. The Script Transformation Editor has up to four pages: Input Columns, Inputs and Outputs, Script, and Connection Managers. The metadata and properties that you configure on each of these pages determines the members of the base classes that are autogenerated for your use in coding. In Script component code, you use typed accessor properties to access certain package features such as variables and connection managers. The PreExecute method can access only read-only variables. The PostExecute method can access both read-only and read/write variables. For more information about these methods, see Coding and Debugging the Script Component.
Execution
Editor
Using variables
Feature
Script Task
Script Component
property of the Dts object to access variables that are available through the tasks
P:Microsoft.SqlServer.Dts.Tasks.ScriptT ask.ScriptTask.ReadOnlyVariables and P:Microsoft.SqlServer.Dts.Tasks.ScriptT ask.ScriptTask.ReadWriteVariables Dim myVar as String myVar = Dts.Variables(MyStringVariable ).Value.ToString string myVar; myVar = Dts.Variables["MyStringVariable "].Value.ToString();
ask.ScriptObjectModel.Variables
For example:
property of the Dts object to access connection managers defined in the package. For example:
The Script component uses typed accessor properties of the autogenerated base class, created from the list of connection managers entered by the user on the Connection Managers page of the editor. For example:
Dim connMgr As IDTSConnectionManager100 connMgr = Me.Connections.MyADONETConnection IDTSConnectionManager100 connMgr; connMgr = this.Connections.MyADONETConnecti on;
Dim myFlatFileConnection As String myFlatFileConnection = _ DirectCast(Dts.Connections("Tes t Flat File Connection").AcquireConnection( Dts.Transaction), _ String) string myFlatFileConnection; myFlatFileConnection = (Dts.Connections["Test Flat File
Feature
Script Task
Script Component
Raising events
The Script component raises errors, warnings, and informational messages by using the methods of the
T:Microsoft.SqlServer.Dts.Pipeline.Wrappe r.IDTSComponentMetaData100 interface
Dts.Events.FireError(0, "Event Snippet", _ ex.Message & ControlChars.CrLf & ex.StackTrace, _ "", 0) Dts.Events.FireError(0, "Event Snippet", ex.Message + "\r" + ex.StackTrace, "", 0);
returned by the
For example:
Logging
of the Dts object to log information to autogenerated base class to log enabled log providers. For example: information to enabled log providers. For example: Dim bt(0) As Byte
Dts.Log("Test Log Event", _ 0, _ bt) byte[] bt = new byte[0]; Dts.Log("Test Log Event", 0, bt);
[Visual Basic]
Dim bt(0) As Byte Me.Log("Test Log Event", _ 0, _ bt) byte[] bt = new byte[0]; this.Log("Test Log Event", 0, bt);
Returning results
The Script component runs as a part of the Data Flow task and does not report results using either of these properties. 8
Feature
Script Task
Script Component
P:Microsoft.SqlServer.Dts.Tasks.ScriptT ask.ScriptObjectModel.ExecutionValue
Stay Up to Date with Integration Services For the latest downloads, articles, samples, and videos from Microsoft, as well as selected solutions from the community, visit the Integration Services page on MSDN:
See Also
Extending the Package with the Script Task Extending the Data Flow with the Script Component
If the functionality is generic, and might be used in the future for other packages and by other developers, you should create a custom object instead of using a scripting solution. You can use a custom object in any package, whereas a script can be used only in the package for which it was created. If the code will be reused within the same package, you should consider creating a custom object. Copying code from one Script task or component to another leads to reduced maintainability by making it more difficult to maintain and update multiple copies of the code. If the implementation will change over time, consider using a custom object. Custom objects can be developed and deployed separately from the parent package, whereas an update made to a scripting solution requires the redeployment of the whole package.
Stay Up to Date with Integration Services For the latest downloads, articles, samples, and videos from Microsoft, as well as selected solutions from the community, visit the Integration Services page on MSDN:
See Also
Extending Packages with Custom Objects
Note To enable your packages to use the objects and methods from a Web service, use the Add Web Reference command available in Microsoft Visual Studio Tools for Applications (VSTA). In earlier versions of Integration Services, you had to generate a proxy class to use a Web service.
System.DirectoryServices Exposes Active Directory. System.Drawing Provides extensive image manipulation libraries. System.Threading Enables multithreaded programming.
For more information about the .NET Framework, see the MSDN Library.
Stay Up to Date with Integration Services For the latest downloads, articles, samples, and videos from Microsoft, as well as selected solutions from the community, visit the Integration Services page on MSDN:
See Also
Procedures
To set a breakpoint in script 1. In SQL Server Data Tools (SSDT), open the Integration Services project that contains the package you want. 2. Double-click the package that contains the script in which you want to set breakpoints. 3. To open the Script task, click the Control Flow tab, and then double-click the Script task. 4. To open the Script component, click the Data Flow tab, and then double-click the Script component. 5. Click Script and then click Edit Script. 6. In Microsoft Visual Studio Tools for Applications (VSTA), locate the line of script on which you want to set a breakpoint, right-click that line, point to Breakpoint, and then click Insert Breakpoint. The breakpoint icon appears on the line of code. 7. On the File menu, click Exit. 8. Click OK. 9. To save the package, click Save Selected Items on the File menu.
If you are creating a task that you plan to reuse in multiple packages, you should consider developing a custom task instead of using the Script task. For more information, see Comparing Scripting Solutions and Custom Objects.
In This Section
The following topics provide more information about the Script task. Configuring the Script Task
Explains how the properties that you configure in the Script Task Editor affect the capabilities and the performance of the code in the Script task.
P:Microsoft.SqlServer.Dts.Tasks.ScriptTask.ScriptObjectModel.Variables
property.
P:Microsoft.SqlServer.Dts.Tasks.ScriptTask.ScriptObjectModel.Connections
property.
P:Microsoft.SqlServer.Dts.Tasks.ScriptTask.ScriptObjectModel.Events property.
P:Microsoft.SqlServer.Dts.Tasks.ScriptTask.ScriptObjectModel.TaskResult and
the
P:Microsoft.SqlServer.Dts.Tasks.ScriptTask.ScriptObjectModel.ExecutionValue
property.
14
Stay Up to Date with Integration Services For the latest downloads, articles, samples, and videos from Microsoft, as well as selected solutions from the community, visit the Integration Services page on MSDN:
See Also
Script Task Comparing the Script Task and the Script Component
ScriptLanguage Property
Microsoft Visual Studio Tools for Applications (VSTA) supports the Microsoft Visual Basic or Microsoft Visual C# programming languages. After you create a script in the Script task, you cannot change value of the ScriptLanguage property. 15
To set the default script language for Script tasks and Script components, use the ScriptLanguage property on the General page of the Options dialog box. For more information, see Select Variables Page (VSTA).
EntryPoint Property
The EntryPoint property specifies the method on the ScriptMain class in the VSTA project that the Integration Services runtime calls as the entry point into the Script task code. The ScriptMain class is the default class generated by the script templates. If you change the name of the method in the VSTA project, you must change the value of the EntryPoint property.
16
Stay Up to Date with Integration Services For the latest downloads, articles, samples, and videos from Microsoft, as well as selected solutions from the community, visit the Integration Services page on MSDN:
See Also
Coding the Script Task
When the Script task is configured for the Visual C# programming language, the ScriptMain class has a public method, Main. The method is called when the Script task runs. By default, the Main method includes the line Dts.TaskResult = (int)ScriptResults.Success. This line informs the runtime that the task was successful in its operation.
The ScriptMain item can contain classes other than the ScriptMain class. Classes are available only to the Script task in which they reside. By default, the ScriptMain project item contains the following autogenerated code. The code template also provides an overview of the Script task, and additional information on how to retrieve and manipulate SSIS objects, such as variables, events, and connections.
' Microsoft SQL Server Integration Services Script Task ' Write scripts using Microsoft Visual Basic 2008. ' The ScriptMain is the entry point class of the script. Imports System Imports System.Data Imports System.Math Imports Microsoft.SqlServer.Dts.Runtime.VSTAProxy
18
<System.AddIn.AddIn("ScriptMain", Version:="1.0", Publisher:="", Description:="")> _ Partial Class ScriptMain Private Sub ScriptMain_Startup(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Startup End Sub Private Sub ScriptMain_Shutdown(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Shutdown Try ' Unlock variables from the read-only and read-write variable collection properties If (Dts.Variables.Count <> 0) Then Dts.Variables.Unlock() End If Catch ex As Exception End Try End Sub Enum ScriptResults Success = DTSExecResult.Success Failure = DTSExecResult.Failure End Enum
' The execution engine calls this method when the task executes. ' To access the object model, use the Dts property. Connections, variables, events, ' and logging features are available as members of the Dts property as shown in the following examples. ' ' To reference a variable, call Dts.Variables("MyCaseSensitiveVariableName").Value
19
' To post a log entry, call Dts.Log("This is my log text", 999, Nothing) ' To fire an event, call Dts.Events.FireInformation(99, "test", "hit the help message", "", 0, True) ' ' To use the connections collection use something like the following: ' ConnectionManager cm = Dts.Connections.Add("OLEDB") ' cm.ConnectionString = "Data Source=localhost;Initial Catalog=AdventureWorks;Provider=SQLNCLI10;Integrated Security=SSPI;Auto Translate=False;" ' ' Before returning from this method, set the value of Dts.TaskResult to indicate success or failure. ' ' To open Help, press F1. Public Sub Main() ' ' Add your code here ' Dts.TaskResult = ScriptResults.Success End Sub End Class /* Microsoft SQL Server Integration Services Script Task Write scripts using Microsoft Visual C# 2008. The ScriptMain is the entry point class of the script. */ using System; using System.Data; using Microsoft.SqlServer.Dts.Runtime.VSTAProxy; using System.Windows.Forms;
20
namespace ST_1bcfdbad36d94f8ba9f23a10375abe53.csproj { [System.AddIn.AddIn("ScriptMain", Version = "1.0", Publisher = "", Description = "")] public partial class ScriptMain { private void ScriptMain_Startup(object sender, EventArgs e) { } private void ScriptMain_Shutdown(object sender, EventArgs e) { try { // Unlock variables from the read-only and read-write variable collection properties if (Dts.Variables.Count != 0) { Dts.Variables.Unlock(); } } catch { } } #region VSTA generated code private void InternalStartup() { this.Startup += new System.EventHandler(ScriptMain_Startup); this.Shutdown += new System.EventHandler(ScriptMain_Shutdown); } enum ScriptResults
21
{ Success = DTSExecResult.Success, Failure = DTSExecResult.Failure }; #endregion /* The execution engine calls this method when the task executes. To access the object model, use the Dts property. Connections, variables, events, and logging features are available as members of the Dts property as shown in the following examples. To reference a variable, call Dts.Variables["MyCaseSensitiveVariableName"].Value; To post a log entry, call Dts.Log("This is my log text", 999, null); To fire an event, call Dts.Events.FireInformation(99, "test", "hit the help message", "", 0, true); To use the connections collection use something like the following: ConnectionManager cm = Dts.Connections.Add("OLEDB"); cm.ConnectionString = "Data Source=localhost;Initial Catalog=AdventureWorks;Provider=SQLNCLI10;Integrated Security=SSPI;Auto Translate=False;"; Before returning from this method, set the value of Dts.TaskResult to indicate success or failure. To open Help, press F1. */ public void Main() { // TODO: Add your code here
22
Dts.TaskResult = (int)ScriptResults.Success; } }
task code through the global Dts object. The topics in this section discuss the use of these members in more detail.
Purpose
Member
Provides access to connection managers defined in the package. Provides an events interface to let the Script task raise errors, warnings, and informational messages. Provides a simple way to return a single object to the runtime (in addition to the TaskResult) that can also be used for workflow branching.
M:Microsoft.SqlServer.Dts.Tasks.ScriptTa Logs information such as task progress and results to sk.ScriptObjectModel.Log(System.String, enabled log providers. System.Int32,System.Byte[])
23
Member
Purpose
Reports the success or failure of the task. Provides the transaction, if any, within which the task's container is running. Provides access to the variables listed in the ReadOnlyVariables and ReadWriteVariables task properties for use within the script.
The T:Microsoft.SqlServer.Dts.Tasks.ScriptTask.ScriptObjectModel class also contains some public members that you will probably not use.
Member Description
P:Microsoft.SqlServer.Dts.Tasks.ScriptTa sk.ScriptObjectModel.VariableDispenser
The
P:Microsoft.SqlServer.Dts.Tasks.ScriptTask.ScriptObject Model.Variables property provides more convenient
methods to lock and unlock variables for reading and writing. The Script task handles locking semantics for you when you use the
Note You cannot debug a Script task when you run the Script task as part of a child package that is run from an Execute Package task. Breakpoints that you set in the Script task in the child package are disregarded in these circumstances. You can debug the child package normally by running it separately. Note When you debug a package that contains multiple Script tasks, the debugger debugs one Script task. The system can debug another Script task if the debugger completes, as in the case of a Foreach Loop or For Loop container.
External Resources
Blog entry, VSTA setup and configuration troubles for SSIS 2008 and R2 installations, on blogs.msdn.com.
Stay Up to Date with Integration Services For the latest downloads, articles, samples, and videos from Microsoft, as well as selected solutions from the community, visit the Integration Services page on MSDN:
For automatic notification of these updates, subscribe to the RSS feeds available on the page.
See Also
Referencing Other Assemblies in Scripting Solutions Configuring the Script Task in the Script Task Editor
The Script task uses the P:Microsoft.SqlServer.Dts.Tasks.ScriptTask.ScriptObjectModel.Variables property of the Dts object to read from and write to T:Microsoft.SqlServer.Dts.Runtime.Variable objects in the package. Note The P:Microsoft.SqlServer.Dts.Runtime.Variable.Value property of the T:Microsoft.SqlServer.Dts.Runtime.Variable class is of type Object. Because the Script task has Option Strict enabled, you must cast the P:Microsoft.SqlServer.Dts.Runtime.Variable.Value property to the appropriate type before you can use it.
P:Microsoft.SqlServer.Dts.Tasks.ScriptTask.ScriptTask.ReadOnlyVariables and P:Microsoft.SqlServer.Dts.Tasks.ScriptTask.ScriptTask.ReadWriteVariables lists in the Script Task
Editor to make them available to the custom script. Keep in mind that variable names are casesensitive. Within the script, you access variables of both types through the P:Microsoft.SqlServer.Dts.Tasks.ScriptTask.ScriptObjectModel.Variables property of the Dts object. Use the Value property to read from and write to individual variables. The Script task transparently manages locking as the script reads and modifies the values of variables. You can use the M:Microsoft.SqlServer.Dts.Runtime.Variables.Contains(System.Object) method of the T:Microsoft.SqlServer.Dts.Runtime.Variables collection returned by the P:Microsoft.SqlServer.Dts.Tasks.ScriptTask.ScriptObjectModel.Variables property to check for the existence of a variable before using it in your code.
P:Microsoft.SqlServer.Dts.Tasks.ScriptTask.ScriptObjectModel.VariableDispenser property
(Dts.VariableDispenser) to work with variables in the Script task. When using the P:Microsoft.SqlServer.Dts.Tasks.ScriptTask.ScriptObjectModel.VariableDispenser, you must handle both the locking semantics and the casting of data types for variable values in your own code. You may need to use the P:Microsoft.SqlServer.Dts.Tasks.ScriptTask.ScriptObjectModel.VariableDispenser property instead of the P:Microsoft.SqlServer.Dts.Tasks.ScriptTask.ScriptObjectModel.Variables property if you want to work with a variable that is not available at design time but is created programmatically at run time.
enumerator returns only a file name at Index 0 and therefore requires only one variable mapping, whereas an enumerator that returns several columns of data in each row requires you to map a different variable to each column that you want to use in the Script task. After you have mapped enumerated items to variables, then you must add the mapped variables to the ReadOnlyVariables property on the Script page of the Script Task Editor to make them available to your script. For an example of a Script task within a Foreach Loop container that processes the image files in a folder, see Script Task Example: Working with Images.
Variables Example
The following example demonstrates how to access and use variables in a Script task to determine the path of package workflow. The sample assumes that you have created integer variables named CustomerCount and MaxRecordCount and added them to the ReadOnlyVariables collection in the Script Task Editor. The CustomerCount variable contains the number of customer records to be imported. If its value is greater than the value of MaxRecordCount, the Script task reports failure. When a failure occurs because the MaxRecordCount threshold has been exceeded, the error path of the workflow can implement any required clean-up. To successfully compile the sample, you need to add a reference to the Microsoft.SqlServer.ScriptTask assembly.
Public Sub Main() Dim customerCount As Integer Dim maxRecordCount As Integer If Dts.Variables.Contains("CustomerCount") = True AndAlso _ Dts.Variables.Contains("MaxRecordCount") = True Then customerCount = _ CType(Dts.Variables("CustomerCount").Value, Integer) maxRecordCount = _ CType(Dts.Variables("MaxRecordCount").Value, Integer) End If If customerCount > maxRecordCount Then Dts.TaskResult = ScriptResults.Failure
27
Else Dts.TaskResult = ScriptResults.Success End If End Sub using System; using System.Data; using Microsoft.SqlServer.Dts.Runtime; public class ScriptMain {
public void Main() { int customerCount; int maxRecordCount; if (Dts.Variables.Contains("CustomerCount")==true&&Dts.Variables.Contains("MaxRe cordCount")==true) { customerCount = (int) Dts.Variables["CustomerCount"].Value; maxRecordCount = (int) Dts.Variables["MaxRecordCount"].Value; } if (customerCount>maxRecordCount) { Dts.TaskResult = (int)ScriptResults.Failure; } else
28
{ Dts.TaskResult = (int)ScriptResults.Success; } } }
Stay Up to Date with Integration Services For the latest downloads, articles, samples, and videos from Microsoft, as well as selected solutions from the community, visit the Integration Services page on MSDN:
See Also
Integration Services Variables Using Variables in Packages
29
method of a connection manager, the connection manager connects to the data source, if it is not already connected, and returns the appropriate connection or connection information for you to use in your Script task code. Note You must know the type of connection returned by the connection manager before calling AcquireConnection. Because the Script task has Option Strict enabled, you must cast the connection, which is returned as type Object, to the appropriate connection type before you can use it. You can use the M:Microsoft.SqlServer.Dts.Runtime.Connections.Contains(System.Object) method of the T:Microsoft.SqlServer.Dts.Runtime.Connections collection returned by the P:Microsoft.SqlServer.Dts.Tasks.ScriptTask.ScriptObjectModel.Connections property to look for an existing connection before using the connection in your code. Important You cannot call the AcquireConnection method of connection managers that return unmanaged objects, such as the OLE DB connection manager and the Excel connection manager, in the managed code of a Script task. However, you can read the ConnectionString property of these connection managers, and connect to the data source directly in your code by using the connection string with an OledbConnection from the System.Data.OleDb namespace. If you must call the AcquireConnection method of a connection manager that returns an unmanaged object, use an ADO.NET connection manager. When you configure the ADO.NET connection manager to use an OLE DB provider, it connects by using the .NET Framework Data Provider for OLE DB. In this case, the AcquireConnection method returns a System.Data.OleDb.OleDbConnection instead of an unmanaged object. To configure an ADO.NET connection manager for use with an Excel data source, select the Microsoft OLE DB Provider for Jet, specify an Excel file, and enter Excel 8.0 (for Excel 97 and later) as the value of Extended Properties on the All page of the Connection Manager dialog box.
Connections Example
The following example demonstrates how to access connection managers from within the Script task. The sample assumes that you have created and configured an ADO.NET connection manager named Test ADO.NET Connection and a Flat File connection manager named Test Flat File Connection. Note that the ADO.NET connection manager returns a SqlConnection object that you can use immediately to connect to the data source. The Flat File connection manager, on the other hand, returns only a string that contains the path and file name. You must use methods from the System.IO namespace to open and work with the flat file.
Public Sub Main()
30
Dim myADONETConnection As SqlClient.SqlConnection myADONETConnection = _ DirectCast(Dts.Connections("Test ADO.NET Connection").AcquireConnection(Dts.Transaction), _ SqlClient.SqlConnection) MsgBox(myADONETConnection.ConnectionString, _ MsgBoxStyle.Information, "ADO.NET Connection") Dim myFlatFileConnection As String myFlatFileConnection = _ DirectCast(Dts.Connections("Test Flat File Connection").AcquireConnection(Dts.Transaction), _ String) MsgBox(myFlatFileConnection, MsgBoxStyle.Information, "Flat File Connection") Dts.TaskResult = ScriptResults.Success End Sub using System; using System.Data.SqlClient; using Microsoft.SqlServer.Dts.Runtime; using System.Windows.Forms; public class ScriptMain {
public void Main() { SqlConnection myADONETConnection = new SqlConnection(); myADONETConnection = (SqlConnection)(Dts.Connections["Test ADO.NET Connection"].AcquireConnection(Dts.Transaction)as SqlConnection);
31
MessageBox.Show(myADONETConnection.ConnectionString, "ADO.NET Connection"); string myFlatFileConnection; myFlatFileConnection = (string)(Dts.Connections["Test Flat File Connection"].AcquireConnection(Dts.Transaction) as String); MessageBox.Show(myFlatFileConnection, "Flat File Connection"); Dts.TaskResult = (int)ScriptResults.Success; } }
Stay Up to Date with Integration Services For the latest downloads, articles, samples, and videos from Microsoft, as well as selected solutions from the community, visit the Integration Services page on MSDN:
See Also
Integration Services Connections Creating Connection Managers
32
To raise an event, the Script task calls one of the methods exposed by the
Event
Description
M:Microsoft.SqlServer.Dts.Runtime.IDTSC Raises a user-defined custom event in the package. omponentEvents.FireCustomEvent(Syste m.String,System.String,System.Object[]@, System.String,System.Boolean@) M:Microsoft.SqlServer.Dts.Runtime.IDTSC Informs the package of an error condition. omponentEvents.FireError(System.Int32,S ystem.String,System.String,System.String, System.Int32) M:Microsoft.SqlServer.Dts.Runtime.IDTSC Provides information to the user. omponentEvents.FireInformation(System. Int32,System.String,System.String,System .String,System.Int32,System.Boolean@) M:Microsoft.SqlServer.Dts.Runtime.IDTSC Informs the package of the progress of the task. omponentEvents.FireProgress(System.Stri ng,System.Int32,System.Int32,System.Int3 2,System.String,System.Boolean@) M:Microsoft.SqlServer.Dts.Runtime.IDTSC Returns a value that indicates whether the package needs the task to shut down prematurely. omponentEvents.FireQueryCancel
33
Event
Description
M:Microsoft.SqlServer.Dts.Runtime.IDTSC Informs the package that the task is in a state that omponentEvents.FireWarning(System.Int warrants user notification, but is not an error 32,System.String,System.String,System.St condition. ring,System.Int32)
Events Example
The following example demonstrates how to raise events from within the Script task. The example uses a native Windows API function to determine whether an Internet connection is available. If no connection is available, it raises an error. If a potentially volatile modem connection is in use, the example raises a warning. Otherwise, it returns an informational message that an Internet connection has been detected.
Private Declare Function InternetGetConnectedState Lib "wininet" _ (ByRef dwFlags As Long, ByVal dwReserved As Long) As Long Private Enum ConnectedStates LAN = &H2 Modem = &H1 Proxy = &H4 Offline = &H20 Configured = &H40 RasInstalled = &H10 End Enum Public Sub Main() Dim dwFlags As Long Dim connectedState As Long Dim fireAgain as Boolean connectedState = InternetGetConnectedState(dwFlags, 0) If connectedState <> 0 Then If (dwFlags And ConnectedStates.Modem) = ConnectedStates.Modem Then
34
Dts.Events.FireWarning(0, "Script Task Example", _ "Volatile Internet connection detected.", String.Empty, 0) Else Dts.Events.FireInformation(0, "Script Task Example", _ "Internet connection detected.", String.Empty, 0, fireAgain) End If Else ' If not connected to the Internet, raise an error. Dts.Events.FireError(0, "Script Task Example", _ "Internet connection not available.", String.Empty, 0) End If Dts.TaskResult = ScriptResults.Success End Sub using System; using System.Data; using Microsoft.SqlServer.Dts.Runtime; using System.Windows.Forms; using System.Runtime.InteropServices; public class ScriptMain {
[DllImport("wininet")] private extern static long InternetGetConnectedState(ref long dwFlags, long dwReserved); private enum ConnectedStates { LAN = 0x2, Modem = 0x1, Proxy = 0x4,
35
Offline = 0x20, Configured = 0x40, RasInstalled = 0x10 }; public void Main() { // long dwFlags = 0; long connectedState; bool fireAgain = true; int state; connectedState = InternetGetConnectedState(ref dwFlags, 0); state = (int)ConnectedStates.Modem; if (connectedState != 0) { if ((dwFlags & state) == state) { Dts.Events.FireWarning(0, "Script Task Example", "Volatile Internet connection detected.", String.Empty, 0); } else { Dts.Events.FireInformation(0, "Script Task Example", "Internet connection detected.", String.Empty, 0, ref fireAgain); } } else { // If not connected to the Internet, raise an error. Dts.Events.FireError(0, "Script Task Example", "Internet connection not available.", String.Empty, 0); }
36
Dts.TaskResult = (int)ScriptResults.Success; }
Stay Up to Date with Integration Services For the latest downloads, articles, samples, and videos from Microsoft, as well as selected solutions from the community, visit the Integration Services page on MSDN:
See Also
Integration Services Event Handlers Add an Event Handler to a Package
M:Microsoft.SqlServer.Dts.Tasks.ScriptTask.ScriptObjectModel.Log(System.String,System.Int32,Sys tem.Byte[]) method stores the event information in all the log providers configured for the task.
Note 37
Although you can perform logging directly from your Script task, you may want to consider implementing events rather than logging. When using events, not only can you enable the logging of event messages, but you can also respond to the event with default or user-defined event handlers. For more information about logging, see Integration Services Logging.
Logging Example
The following example demonstrates logging from the Script task by logging a value that represents the number of rows processed.
Public Sub Main() Dim rowsProcessed As Integer = 100 Dim emptyBytes(0) As Byte Try Dts.Log("Rows processed: " & rowsProcessed.ToString, _ 0, _ emptyBytes) Dts.TaskResult = ScriptResults.Success Catch ex As Exception 'An error occurred. Dts.Events.FireError(0, "Script Task Example", _ ex.Message & ControlChars.CrLf & ex.StackTrace, _ String.Empty, 0) Dts.TaskResult = ScriptResults.Failure End Try End Sub using System; using System.Data; using Microsoft.SqlServer.Dts.Runtime; public class ScriptMain {
38
public void Main() { // int rowsProcessed = 100; byte[] emptyBytes = new byte[0]; try { Dts.Log("Rows processed: " + rowsProcessed.ToString(), 0, emptyBytes); Dts.TaskResult = (int)ScriptResults.Success; } catch (Exception ex) { //An error occurred. Dts.Events.FireError(0, "Script Task Example", ex.Message + "\r" + ex.StackTrace, String.Empty, 0); Dts.TaskResult = (int)ScriptResults.Failure; } }
External Resources
Blog entry, Logging custom events for Integration Services tasks, on dougbert.com
39
Stay Up to Date with Integration Services For the latest downloads, articles, samples, and videos from Microsoft, as well as selected solutions from the community, visit the Integration Services page on MSDN:
For automatic notification of these updates, subscribe to the RSS feeds available on the page.
See Also
Integration Services Logging
TaskResult
The P:Microsoft.SqlServer.Dts.Tasks.ScriptTask.ScriptObjectModel.TaskResult property reports whether the task succeeded or failed. For example:
Dts.TaskResult = ScriptResults.Success
ExecutionValue
The P:Microsoft.SqlServer.Dts.Tasks.ScriptTask.ScriptObjectModel.ExecutionValue property optionally returns a user-defined object that quantifies or provides more information about the success or failure of the Script task. For example, the FTP task uses the P:Microsoft.SqlServer.Dts.Tasks.ScriptTask.ScriptObjectModel.ExecutionValue property to return the number of files transferred. The Execute SQL task returns the number of rows affected by the task. The P:Microsoft.SqlServer.Dts.Tasks.ScriptTask.ScriptObjectModel.ExecutionValue can also be used to determine the path of the workflow. For example:
Dim rowsAffected as Integer
40
Stay Up to Date with Integration Services For the latest downloads, articles, samples, and videos from Microsoft, as well as selected solutions from the community, visit the Integration Services page on MSDN:
41
Other Examples
The following topics also contain code examples for use with the Script task: Variables in the Script Task
Asks the user for confirmation of whether the package should continue to run, based on the value of a package variable that may exceed the limit specified in another variable.
42
Stay Up to Date with Integration Services For the latest downloads, articles, samples, and videos from Microsoft, as well as selected solutions from the community, visit the Integration Services page on MSDN:
Description
The following example uses methods from the System.IO namespace to test the flat file specified in a Flat File connection manager to determine whether the file is empty, or whether it contains only expected non-data rows such as column headers or an empty line. The script checks the size of the file first; if the size is zero bytes, the file is empty. If the file size is greater than zero, the script reads lines from the file until there are no more lines, or until the number of lines exceeds the expected number of non-data rows. If the number of lines in the file is less than or equal to the expected number of non-data rows, then the file is considered empty. The result is returned as a Boolean value in a user variable, the value of which can be used for branching in the package's control flow. The FireInformation method also displays the result in the Output window of the Microsoft Visual Studio Tools for Applications (VSTA).
43
To configure this Script Task example 1. Create and configure a flat file connection manager named EmptyFlatFileTest. 2. Create an integer variable named FFNonDataRows and set its value to the number of non-data rows expected in the flat file. 3. Create a Boolean variable named FFIsEmpty. 4. Add the FFNonDataRows variable to the Script task's ReadOnlyVariables property. 5. Add the FFIsEmpty variable to the Script task's ReadWriteVariables property. 6. In your code, import the System.IO namespace. If you are iterating over files with a Foreach File enumerator, instead of using a single Flat File connection manager, you will need to modify the sample code below to obtain the file name and path from the variable in which the enumerated value is stored instead of from the connection manager.
Code
Public Sub Main() Dim nonDataRows As Integer = _ DirectCast(Dts.Variables("FFNonDataRows").Value, Integer) Dim ffConnection As String = _ DirectCast(Dts.Connections("EmptyFlatFileTest").AcquireConnection(Nothing), _ String) Dim flatFileInfo As New FileInfo(ffConnection) ' If file size is 0 bytes, flat file does not contain data. Dim fileSize As Long = flatFileInfo.Length If fileSize > 0 Then Dim lineCount As Integer = 0 Dim line As String Dim fsFlatFile As New StreamReader(ffConnection) Do Until fsFlatFile.EndOfStream line = fsFlatFile.ReadLine lineCount += 1 ' If line count > expected number of non-data rows, ' flat file contains data (default value). If lineCount > nonDataRows Then
44
Exit Do End If ' If line count <= expected number of non-data rows, ' flat file does not contain data. Dts.Variables("FFIsEmpty").Value = True End If Loop Else Dts.Variables("FFIsEmpty").Value = True End If Dim fireAgain As Boolean = False Dts.Events.FireInformation(0, "Script Task", _ String.Format("{0}: {1}", ffConnection, _ Dts.Variables("FFIsEmpty").Value.ToString), _ String.Empty, 0, fireAgain) Dts.TaskResult = ScriptResults.Success End Sub public void Main() { int nonDataRows = (int)(Dts.Variables["FFNonDataRows"].Value); string ffConnection = (string)(Dts.Connections["EmptyFlatFileTest"].AcquireConnection(null) as String); FileInfo flatFileInfo = new FileInfo(ffConnection); // If file size is 0 bytes, flat file does not contain data. long fileSize = flatFileInfo.Length; if (fileSize > 0) { If lineCount <= nonDataRows Then
45
int lineCount = 0; string line; StreamReader fsFlatFile = new StreamReader(ffConnection); while (!(fsFlatFile.EndOfStream)) { Console.WriteLine (fsFlatFile.ReadLine()); lineCount += 1; // If line count > expected number of non-data rows, // { break; } // If line count <= expected number of non-data rows, // { Dts.Variables["FFIsEmpty"].Value = true; } } } else { Dts.Variables["FFIsEmpty"].Value = true; } bool fireAgain = false; Dts.Events.FireInformation(0, "Script Task", String.Format("{0}: {1}", ffConnection, Dts.Variables["FFIsEmpty"].Value), String.Empty, 0, ref fireAgain); Dts.TaskResult = (int)ScriptResults.Success; flat file does not contain data. if (lineCount <= nonDataRows) flat file contains data (default value). if (lineCount > nonDataRows)
46
Stay Up to Date with Integration Services For the latest downloads, articles, samples, and videos from Microsoft, as well as selected solutions from the community, visit the Integration Services page on MSDN:
See Also
Script Task Examples
Gathering a List for the ForEach Loop with the Script Task
The Foreach from Variable Enumerator enumerates over the items in a list that is passed to it in a variable and performs the same tasks on each item. You can use custom code in a Script task to populate a list for this purpose. For more information about the enumerator, see Foreach Loop Container. Note If you want to create a task that you can more easily reuse across multiple packages, consider using the code in this Script task sample as the starting point for a custom task. For more information, see Extending the Package with Custom Tasks.
Description
The following example uses methods from the System.IO namespace to gather a list of Excel workbooks on the computer that are either newer or older than a number of days specified by the user in a variable. It searches directories on Drive C recursively for files that have the .xls extension and examines the date on which each file was last modified to determine whether the file belongs in the list. It adds qualifying files to an ArrayList and saves the ArrayList to a
47
variable for later use in a Foreach Loop container. The Foreach Loop container is configured to use the Foreach from Variable enumerator. Note The variable that you use with the Foreach from Variable Enumerator must be of type Object. The object that you place in the variable must implement one of the following interfaces: System.Collections.IEnumerable, System.Runtime.InteropServices.ComTypes.IEnumVARIANT, System.ComponentModel IListSource, or Microsoft.SqlServer.Dts.Runtime.Wrapper.ForEachEnumeratorHost. An Array or ArrayList is commonly used. The ArrayList requires a reference and an Imports statement for the System.Collections namespace.
FileAge package variable. For example, you can enter 5 to search for files created in the last five
You can experiment with this task by using different positive and negative values for the
days, or enter -3 to search for files that were created more than three days ago. This task may take a minute or two on a drive with many folders to search. To configure this Script Task example 1. Create a package variable named FileAge of type integer and enter a positive or negative integer value. When the value is positive, the code searches for files newer than the specified number of days; when negative, for files older than the specified number of days. 2. Create a package variable named FileList of type Object to receive the list of files gathered by the Script task for later use by the Foreach from Variable Enumerator. 3. Add the FileAge variable to the Script task's ReadOnlyVariables property, and add the FileList variable to the ReadWriteVariables property. 4. In your code, import the System.Collections and the System.IO namespaces.
Code
Imports System Imports System.Data Imports System.Math Imports Microsoft.SqlServer.Dts.Runtime Imports System.Collections Imports System.IO Public Class ScriptMain Private Const FILE_AGE As Integer = -50
48
Private Const FILE_ROOT As String = "C:\" Private Const FILE_FILTER As String = "*.xls" Private isCheckForNewer As Boolean = True Dim fileAgeLimit As Integer Private listForEnumerator As ArrayList Public Sub Main() fileAgeLimit = DirectCast(Dts.Variables("FileAge").Value, Integer) ' If value provided is positive, we want files NEWER THAN n days. ' If negative, we want files OLDER THAN n days. isCheckForNewer = False End If ' Extract number of days as positive integer. fileAgeLimit = Math.Abs(fileAgeLimit) listForEnumerator = New ArrayList GetFilesInFolder(FILE_ROOT) ' Return the list of files to the variable ' for later use by the Foreach from Variable enumerator. System.Windows.Forms.MessageBox.Show("Matching files: " & listForEnumerator.Count.ToString, "Results", Windows.Forms.MessageBoxButtons.OK, Windows.Forms.MessageBoxIcon.Information) Dts.Variables("FileList").Value = listForEnumerator Dts.TaskResult = ScriptResults.Success End Sub If fileAgeLimit < 0 Then
49
Private Sub GetFilesInFolder(ByVal folderPath As String) Dim localFiles() As String Dim localFile As String Dim fileChangeDate As Date Dim fileAge As TimeSpan Dim fileAgeInDays As Integer Dim childFolder As String Try localFiles = Directory.GetFiles(folderPath, FILE_FILTER) For Each localFile In localFiles fileChangeDate = File.GetLastWriteTime(localFile) fileAge = DateTime.Now.Subtract(fileChangeDate) fileAgeInDays = fileAge.Days CheckAgeOfFile(localFile, fileAgeInDays) Next If Directory.GetDirectories(folderPath).Length > 0 Then For Each childFolder In Directory.GetDirectories(folderPath) GetFilesInFolder(childFolder) Next End If Catch ' Ignore exceptions on special folders such as System Volume Information. End Try End Sub Private Sub CheckAgeOfFile(ByVal localFile As String, ByVal fileAgeInDays As Integer)
50
If isCheckForNewer Then If fileAgeInDays <= fileAgeLimit Then listForEnumerator.Add(localFile) End If Else If fileAgeInDays > fileAgeLimit Then listForEnumerator.Add(localFile) End If End If End Sub End Class using System; using System.Data; using System.Math; using Microsoft.SqlServer.Dts.Runtime; using System.Collections; using System.IO; public partial class ScriptMain : Microsoft.SqlServer.Dts.Tasks.ScriptTask.VSTARTScriptObjectModelBase {
private const int FILE_AGE = -50; private const string FILE_ROOT = "C:\\"; private const string FILE_FILTER = "*.xls"; private bool isCheckForNewer = true; int fileAgeLimit; private ArrayList listForEnumerator;
51
public void Main() { fileAgeLimit = (int)(Dts.Variables["FileAge"].Value); // If value provided is positive, we want files NEWER THAN n days. // If negative, we want files OLDER THAN n days. if (fileAgeLimit<0) { isCheckForNewer = false; } // Extract number of days as positive integer. fileAgeLimit = Math.Abs(fileAgeLimit); ArrayList listForEnumerator = new ArrayList(); GetFilesInFolder(FILE_ROOT); // Return the list of files to the variable // for later use by the Foreach from Variable enumerator. System.Windows.Forms.MessageBox.Show("Matching files: "+ listForEnumerator.Count, "Results", MessageBoxButtons.OK, MessageBoxIcon.Information); Dts.Variables["FileList"].Value = listForEnumerator; Dts.TaskResult = (int)ScriptResults.Success; } private void GetFilesInFolder(string folderPath) { string[] localFiles;
52
DateTime fileChangeDate; TimeSpan fileAge; int fileAgeInDays; try { localFiles = Directory.GetFiles(folderPath, FILE_FILTER); foreach (string localFile in localFiles) { fileChangeDate = File.GetLastWriteTime(localFile); fileAge = DateTime.Now.Subtract(fileChangeDate); fileAgeInDays = fileAge.Days; CheckAgeOfFile(localFile, fileAgeInDays); } if (Directory.GetDirectories(folderPath).Length > 0) { foreach (string childFolder in Directory.GetDirectories(folderPath)) { GetFilesInFolder(childFolder); } } } catch { // Ignore exceptions on special folders, such as System Volume Information. } } private void CheckAgeOfFile(string localFile, int fileAgeInDays)
53
{ if (isCheckForNewer) { if (fileAgeInDays <= fileAgeLimit) { listForEnumerator.Add(localFile); } } else { if (fileAgeInDays > fileAgeLimit) { listForEnumerator.Add(localFile); } } } }
54
Stay Up to Date with Integration Services For the latest downloads, articles, samples, and videos from Microsoft, as well as selected solutions from the community, visit the Integration Services page on MSDN:
See Also
Foreach Loop Container How to: Configure a Foreach Loop Container
Description
The following example retrieves an employee's name, title, and phone number from Active Directory based on the value of the email variable, which contains the e-mail address of the employee. Precedence constraints in the package can use the retrieved information to determine, for example, whether to send a low-priority e-mail message or a high-priority page, based on the job title of the employee. 55
To configure this Script Task example 1. Create the three string variables email, name, and title. Enter a valid corporate email address as the value of the email variable. 2. On the Script page of the Script Task Editor, add the email variable to the ReadOnlyVariables property. 3. Add the name and title variables to the ReadWriteVariables property. 4. In the script project, add a reference to the System.DirectoryServices namespace. 5. . In your code, use an Imports statement to import the DirectoryServices namespace. Note To run this script successfully, your company must be using Active Directory on its network and storing the employee information that this example uses.
Code
Public Sub Main() Dim directory As DirectoryServices.DirectorySearcher Dim result As DirectoryServices.SearchResult Dim email As String email = Dts.Variables("email").Value.ToString Try directory = New _ DirectoryServices.DirectorySearcher("(mail=" & email & ")") result = directory.FindOne Dts.Variables("name").Value = _ result.Properties("displayname").ToString Dts.Variables("title").Value = _ result.Properties("title").ToString Dts.TaskResult = ScriptResults.Success Catch ex As Exception Dts.Events.FireError(0, _ "Script Task Example", _ ex.Message & ControlChars.CrLf & ex.StackTrace, _
56
String.Empty, 0) Dts.TaskResult = ScriptResults.Failure End Try End Sub public void Main() { // DirectorySearcher directory; SearchResult result; string email; email = (string)Dts.Variables["email"].Value; try { directory = new DirectorySearcher("(mail=" + email + ")"); result = directory.FindOne(); Dts.Variables["name"].Value = result.Properties["displayname"].ToString(); Dts.Variables["title"].Value = result.Properties["title"].ToString(); Dts.TaskResult = (int)ScriptResults.Success; } catch (Exception ex) { Dts.Events.FireError(0, "Script Task Example", ex.Message + "\n" + ex.StackTrace, String.Empty, 0); Dts.TaskResult = (int)ScriptResults.Failure; } }
External Resources
57
Stay Up to Date with Integration Services For the latest downloads, articles, samples, and videos from Microsoft, as well as selected solutions from the community, visit the Integration Services page on MSDN:
For automatic notification of these updates, subscribe to the RSS feeds available on the page.
The
progress and percentage complete information to the Integration Services runtime. Note
58
If you want to create a task that you can more easily reuse across multiple packages, consider using the code in this Script task sample as the starting point for a custom task. For more information, see Extending the Package with Custom Tasks.
Description
The following example creates a custom performance counter and increments the counter. First, the example determines whether the performance counter already exists. If the performance counter has not been created, the script calls the Create method of the PerformanceCounterCategory object to create it. After the performance counter has been created, the script increments the counter. Finally, the example follows the best practice of calling the Close method on the performance counter when it is no longer needed. Note Creating a new performance counter category and performance counter requires administrative rights. Also, the new category and counter persist on the computer after creation. To configure this Script Task example Use an Imports statement in your code to import the System.Diagnostics namespace.
Example Code
Public Sub Main() Dim myCounter As PerformanceCounter Try 'Create the performance counter if it does not already exist. If Not _ PerformanceCounterCategory.Exists("TaskExample") Then PerformanceCounterCategory.Create("TaskExample", _ "Task Performance Counter Example", "Iterations", _ "Number of times this task has been called.") End If 'Initialize the performance counter. myCounter = New PerformanceCounter("TaskExample", _ "Iterations", String.Empty, False) 'Increment the performance counter.
59
myCounter.Increment() myCounter.Close() Dts.TaskResult = ScriptResults.Success Catch ex As Exception Dts.Events.FireError(0, _ "Task Performance Counter Example", _ ex.Message & ControlChars.CrLf & ex.StackTrace, _ String.Empty, 0) Dts.TaskResult = ScriptResults.Failure End Try End Sub public class ScriptMain {
public void Main() { PerformanceCounter myCounter; try { //Create the performance counter if it does not already exist. if (!PerformanceCounterCategory.Exists("TaskExample")) { PerformanceCounterCategory.Create("TaskExample", "Task Performance Counter Example", "Iterations", "Number of times this task has been called."); }
60
//Initialize the performance counter. myCounter = new PerformanceCounter("TaskExample", "Iterations", String.Empty, false); //Increment the performance counter. myCounter.Increment(); myCounter.Close(); Dts.TaskResult = (int)ScriptResults.Success; } catch (Exception ex) { Dts.Events.FireError(0, "Task Performance Counter Example", ex.Message + "\r" + ex.StackTrace, String.Empty, 0); Dts.TaskResult = (int)ScriptResults.Failure; } Dts.TaskResult = (int)ScriptResults.Success; }
61
Stay Up to Date with Integration Services For the latest downloads, articles, samples, and videos from Microsoft, as well as selected solutions from the community, visit the Integration Services page on MSDN:
4. In your code, use Imports statements to import the System.Drawing and System.IO namespaces. To configure this Script Task example for use with multiple image files 1. Place the Script task within a Foreach Loop container. 2. On the Collection page of the Foreach Loop Editor, select the Foreach File Enumerator as the enumerator, and specify the path and file mask of the source files, such as "*.bmp." 3. On the Variable Mappings page, map the CurrentImageFile variable to Index 0. This variable passes the current file name to the Script task on each iteration of the enumerator. Note These steps are in addition to the steps listed in the procedure for use with a single image file.
Example 1 Code
Public Sub Main() 'Create and initialize variables. Dim currentFile As String Dim newFile As String Dim bmp As Bitmap Dim eps As New Imaging.EncoderParameters(1) Dim ici As Imaging.ImageCodecInfo Dim supportedExtensions() As String = _ {".BMP", ".GIF", ".JPG", ".JPEG", ".EXIF", ".PNG", _ ".TIFF", ".TIF", ".ICO", ".ICON"} Try 'Store the variable in a string for local manipulation. currentFile = Dts.Variables("CurrentImageFile").Value.ToString 'Check the extension of the file against a list of 'files that the Bitmap class supports. If Array.IndexOf(supportedExtensions, _ Path.GetExtension(currentFile).ToUpper) > -1 Then
63
'Load the file. bmp = New Bitmap(currentFile) 'Calculate the new name for the compressed image. 'Note: This will overwrite existing JPEGs. newFile = Path.Combine( _ Path.GetDirectoryName(currentFile), _ String.Concat(Path.GetFileNameWithoutExtension(currentFile), _ ".jpg")) 'Specify the compression ratio (0=worst quality, 100=best quality). eps.Param(0) = New Imaging.EncoderParameter( _ Imaging.Encoder.Quality, 75) 'Retrieve the ImageCodecInfo associated with the jpeg format. ici = GetEncoderInfo("image/jpeg") 'Save the file, compressing it into the jpeg encoding. bmp.Save(newFile, ici, eps) Else 'The file is not supported by the Bitmap class. Dts.Events.FireWarning(0, "Image Resampling Sample", _ "File " & currentFile & " is not a supported format.", _ "", 0) End If Dts.TaskResult = ScriptResults.Success Catch ex As Exception 'An error occurred. Dts.Events.FireError(0, "Image Resampling Sample", _ ex.Message & ControlChars.CrLf & ex.StackTrace, _ String.Empty, 0) Dts.TaskResult = ScriptResults.Failure
64
End Try End Sub Private Function GetEncoderInfo(ByVal mimeType As String) As Imaging.ImageCodecInfo 'The available image codecs are returned as an array, 'which requires code to iterate until the specified codec is found. Dim count As Integer Dim encoders() As Imaging.ImageCodecInfo encoders = Imaging.ImageCodecInfo.GetImageEncoders() For count = 0 To encoders.Length If encoders(count).MimeType = mimeType Then Return encoders(count) End If Next 'This point is only reached if a codec is not found. Err.Raise(513, "Image Resampling Sample", String.Format( _ "The {0} codec is not available. Unable to compress file.", _ mimeType)) Return Nothing End Function
To configure this Script Task example for use with a single image file 1. Create a string variable named CurrentImageFile and set its value to the path and file name of an existing image file. 2. Also create the MaxThumbSize integer variable and assign a value in pixels, such as 100. 3. On the Script page of the Script Task Editor, add both variables to the ReadOnlyVariables property. 4. In the script project, set a reference to the System.Drawing namespace. 5. In your code, use Imports statements to import the System.Drawing and System.IO namespaces. To configure this Script Task example for use with multiple image files 1. Place the Script task within a Foreach Loop container. 2. On the Collection page of the Foreach Loop Editor, select the Foreach File Enumerator as the Enumerator, and specify the path and file mask of the source files, such as "*.jpg." 3. On the Variable Mappings page, map the CurrentImageFile variable to Index 0. This variable passes the current file name to the Script task on each iteration of the enumerator. Note These steps are in addition to the steps listed in the procedure for use with a single image file.
Example 2 Code
Public Sub Main() Dim currentImageFile As String Dim currentImage As Image Dim maxThumbSize As Integer Dim thumbnailImage As Image Dim thumbnailFile As String Dim thumbnailHeight As Integer Dim thumbnailWidth As Integer currentImageFile = Dts.Variables("CurrentImageFile").Value.ToString thumbnailFile = Path.Combine( _ Path.GetDirectoryName(currentImageFile), _
66
String.Concat(Path.GetFileNameWithoutExtension(currentImageFile), _ "_thumbnail.jpg")) Try currentImage = Image.FromFile(currentImageFile) maxThumbSize = CType(Dts.Variables("MaxThumbSize").Value, Integer) CalculateThumbnailSize( _ maxThumbSize, currentImage, thumbnailWidth, thumbnailHeight) thumbnailImage = currentImage.GetThumbnailImage( _ thumbnailWidth, thumbnailHeight, Nothing, Nothing) thumbnailImage.Save(thumbnailFile) Dts.TaskResult = ScriptResults.Success Catch ex As Exception Dts.Events.FireError(0, "Script Task Example", _ ex.Message & ControlChars.CrLf & ex.StackTrace, _ String.Empty, 0) Dts.TaskResult = ScriptResults.Failure End Try End Sub Private Sub CalculateThumbnailSize( _ ByVal maxSize As Integer, ByVal sourceImage As Image, _ ByRef thumbWidth As Integer, ByRef thumbHeight As Integer) If sourceImage.Width > sourceImage.Height Then thumbWidth = maxSize thumbHeight = CInt((maxSize / sourceImage.Width) * sourceImage.Height) Else thumbHeight = maxSize thumbWidth = CInt((maxSize / sourceImage.Height) * sourceImage.Width)
67
End If End Sub bool ThumbnailCallback() { return false; } public void Main() { string currentImageFile; Image currentImage; int maxThumbSize; Image thumbnailImage; string thumbnailFile; int thumbnailHeight = 0; int thumbnailWidth = 0;
currentImageFile = Dts.Variables["CurrentImageFile"].Value.ToString(); thumbnailFile = Path.Combine(Path.GetDirectoryName(currentImageFile), String.Concat(Path.GetFileNameWithoutExtension(currentImageFile), "_thumbnail.jpg")); try { currentImage = Image.FromFile(currentImageFile); maxThumbSize = (int)Dts.Variables["MaxThumbSize"].Value; CalculateThumbnailSize(maxThumbSize, currentImage, ref thumbnailWidth, ref thumbnailHeight);
68
Image.GetThumbnailImageAbort myCallback = new Image.GetThumbnailImageAbort(ThumbnailCallback); thumbnailImage = currentImage.GetThumbnailImage(thumbnailWidth, thumbnailHeight, ThumbnailCallback, IntPtr.Zero); thumbnailImage.Save(thumbnailFile); Dts.TaskResult = (int)ScriptResults.Success; } catch (Exception ex) { Dts.Events.FireError(0, "Script Task Example", ex.Message + "\r" + ex.StackTrace, String.Empty, 0); Dts.TaskResult = (int)ScriptResults.Failure; } } private void CalculateThumbnailSize(int maxSize, Image sourceImage, ref int thumbWidth, ref int thumbHeight) { if (sourceImage.Width > sourceImage.Height) { thumbWidth = maxSize; thumbHeight = (int)(sourceImage.Height * maxSize / sourceImage.Width); } else { thumbHeight = maxSize; thumbWidth = (int)(sourceImage.Width * maxSize / sourceImage.Height);
69
} }
Stay Up to Date with Integration Services For the latest downloads, articles, samples, and videos from Microsoft, as well as selected solutions from the community, visit the Integration Services page on MSDN:
Description
The following example locates printers installed on the server that support legal size paper (as used in the United States). The code to check supported paper sizes is encapsulated in a private function. To enable you to track the progress of the script as it checks the settings for each printer, the script uses the raise a warning for printers without legal size paper. These messages appear in the Output
M:Microsoft.SqlServer.Dts.Tasks.ScriptTask.ScriptObjectModel.Log(System.String,System.Int32,Sys tem.Byte[]) method to raise an informational message for printers with legal size paper, and to
70
window of the Microsoft Visual Studio Tools for Applications (VSTA) IDE when you run the package in the designer. To configure this Script Task example 1. Create the variable named PrinterList with type Object. 2. On the Script page of the Script Task Editor, add this variable to the ReadWriteVariables property. 3. In the script project, add a reference to the System.Drawing namespace. 4. In your code, use Imports statements to import the System.Collections and the System.Drawing.Printing namespaces.
Code
Public Sub Main() Dim printerName As String Dim currentPrinter As New PrinterSettings Dim size As PaperSize Dim printerList As New ArrayList For Each printerName In PrinterSettings.InstalledPrinters currentPrinter.PrinterName = printerName If PrinterHasLegalPaper(currentPrinter) Then printerList.Add(printerName) Dts.Events.FireInformation(0, "Example", _ "Printer " & printerName & " has legal paper.", _ String.Empty, 0, False) Else Dts.Events.FireWarning(0, "Example", _ "Printer " & printerName & " DOES NOT have legal paper.", _ String.Empty, 0) End If Next Dts.Variables("PrinterList").Value = printerList
71
Dts.TaskResult = ScriptResults.Success End Sub Private Function PrinterHasLegalPaper( _ ByVal thisPrinter As PrinterSettings) As Boolean Dim size As PaperSize Dim hasLegal As Boolean = False For Each size In thisPrinter.PaperSizes If size.Kind = PaperKind.Legal Then hasLegal = True End If Next Return hasLegal End Function public void Main() { PrinterSettings currentPrinter = new PrinterSettings(); PaperSize size; Boolean Flag = false; ArrayList printerList = new ArrayList(); foreach (string printerName in PrinterSettings.InstalledPrinters) { currentPrinter.PrinterName = printerName; if (PrinterHasLegalPaper(currentPrinter)) { printerList.Add(printerName);
72
Dts.Events.FireInformation(0, "Example", "Printer " + printerName + " has legal paper.", String.Empty, 0, ref Flag); } else { Dts.Events.FireWarning(0, "Example", "Printer " + printerName + " DOES NOT have legal paper.", String.Empty, 0); } } Dts.Variables["PrinterList"].Value = printerList; Dts.TaskResult = (int)ScriptResults.Success; } private bool PrinterHasLegalPaper(PrinterSettings thisPrinter) { bool hasLegal = false; foreach (PaperSize size in thisPrinter.PaperSizes) { if (size.Kind == PaperKind.Legal) { hasLegal = true; } } return hasLegal; }
73
Stay Up to Date with Integration Services For the latest downloads, articles, samples, and videos from Microsoft, as well as selected solutions from the community, visit the Integration Services page on MSDN:
See Also
Script Task Examples
Description
The following example uses the System.Net.Mail namespace to configure and send an HTML mail message. The script obtains the To, From, Subject, and body of the e-mail from package variables, uses them to create a new MailMessage, and sets its IsBodyHtml property to True. Then it obtains the SMTP server name from another package variable, initializes an instance of System.Net.Mail.SmtpClient, and calls its Send method to send the HTML message. The sample encapsulates the message sending functionality in a subroutine that could be reused in other scripts. To configure this Script Task example without an SMTP Connection Manager 1. Create string variables named HtmlEmailTo, HtmlEmailFrom, and HtmlEmailSubject 74
and assign appropriate values to them for a valid test message. 2. Create a string variable named HtmlEmailBody and assign a string of HTML markup to it. For example:
<html><body><h1>Testing</h1><p>This is a <b>test</b> message.</p></body></html>
3. Create a string variable named HtmlEmailServer and assign the name of an available SMTP server that accepts anonymous outgoing messages. 4. Assign all five of these variables to the ReadOnlyVariables property of a new Script task. 5. Import the System.Net and System.Net.Mail namespaces into your code. The sample code in this topic obtains the SMTP server name from a package variable. However, you could also take advantage of an SMTP connection manager to encapsulate the connection information, and extract the server name from the connection manager in your code. The
M:Microsoft.SqlServer.Dts.ManagedConnections.SMTPConn.AcquireConnection(System.Object)
method of the SMTP connection manager returns a string in the following format:
SmtpServer=smtphost;UseWindowsAuthentication=False;EnableSsl=False;
You can use the String.Split method to separate this argument list into an array of individual strings at each semicolon (;) or equal sign (=), and then extract the second argument (subscript 1) from the array as the server name. To configure this Script Task example with an SMTP Connection Manager 1. Modify the Script task configured earlier by removing the HtmlEmailServer variable from the list of ReadOnlyVariables. 2. Replace the line of code that obtains the server name:
Dim smtpServer As String = _ Dts.Variables("HtmlEmailServer").Value.ToString
Code
Public Sub Main() Dim htmlMessageTo As String = _
75
Dts.Variables("HtmlEmailTo").Value.ToString Dim htmlMessageFrom As String = _ Dts.Variables("HtmlEmailFrom").Value.ToString Dim htmlMessageSubject As String = _ Dts.Variables("HtmlEmailSubject").Value.ToString Dim htmlMessageBody As String = _ Dts.Variables("HtmlEmailBody").Value.ToString Dim smtpServer As String = _ Dts.Variables("HtmlEmailServer").Value.ToString SendMailMessage( _ htmlMessageTo, htmlMessageFrom, _ htmlMessageSubject, htmlMessageBody, _ True, smtpServer) Dts.TaskResult = ScriptResults.Success End Sub Private Sub SendMailMessage( _ ByVal SendTo As String, ByVal From As String, _ ByVal Subject As String, ByVal Body As String, _ ByVal IsBodyHtml As Boolean, ByVal Server As String) Dim htmlMessage As MailMessage Dim mySmtpClient As SmtpClient htmlMessage = New MailMessage( _ SendTo, From, Subject, Body) htmlMessage.IsBodyHtml = IsBodyHtml mySmtpClient = New SmtpClient(Server) mySmtpClient.Credentials = CredentialCache.DefaultNetworkCredentials
76
mySmtpClient.Send(htmlMessage) End Sub public void Main() { string htmlMessageTo = Dts.Variables["HtmlEmailTo"].Value.ToString(); string htmlMessageFrom = Dts.Variables["HtmlEmailFrom"].Value.ToString(); string htmlMessageSubject = Dts.Variables["HtmlEmailSubject"].Value.ToString(); string htmlMessageBody = Dts.Variables["HtmlEmailBody"].Value.ToString(); string smtpServer = Dts.Variables["HtmlEmailServer"].Value.ToString(); SendMailMessage(htmlMessageTo, htmlMessageFrom, htmlMessageSubject, htmlMessageBody, true, smtpServer); Dts.TaskResult = (int)ScriptResults.Success; } private void SendMailMessage(string SendTo, string From, string Subject, string Body, bool IsBodyHtml, string Server) { MailMessage htmlMessage; SmtpClient mySmtpClient; htmlMessage = new MailMessage(SendTo, From, Subject, Body); htmlMessage.IsBodyHtml = IsBodyHtml; mySmtpClient = new SmtpClient(Server);
77
Stay Up to Date with Integration Services For the latest downloads, articles, samples, and videos from Microsoft, as well as selected solutions from the community, visit the Integration Services page on MSDN:
See Also
Send Mail Task
Note If you want to create a task that you can more easily reuse across multiple packages, consider using the code in this Script task sample as the starting point for a custom task. For more information, see Extending the Package with Custom Tasks.
case-sensitive.
ExcelFileExists, of type Boolean. ExcelTableExists, of type Boolean. ExcelFolder, of type String. Enter the complete path of a folder that contains at
3. Imports statements. Most of the code samples require you to import one or both of the following .NET Framework namespaces at the top of your script file: System.IO, for file system operations. System.Data.OleDb, to open Excel files as data sources.
4. References. The code samples that read schema information from Excel files require an additional reference in the script project to the System.Xml namespace. 5. Set the default scripting language for the Script component by using the Scripting language option on the General page of the Options dialog box. For more information, see General Page.
1. Add a new Script task to the package and change its name to ExcelFileExists. 2. In the Script Task Editor, on the Script tab, click ReadOnlyVariables and enter the property value using one of the following methods: Type ExcelFile. -orClick the ellipsis () button next to the property field, and in the Select variables dialog box, select the ExcelFile variable.
3. Click ReadWriteVariables and enter the property value using one of the following methods: Type ExcelFileExists. -orClick the ellipsis () button next to the property field, and in the Select variables dialog box, select the ExcelFileExists variable.
4. Click Edit Script to open the script editor. 5. Add an Imports statement for the System.IO namespace at the top of the script file. 6. Add the following code.
Example 1 Code
Public Class ScriptMain Public Sub Main() Dim fileToTest As String fileToTest = Dts.Variables("ExcelFile").Value.ToString If File.Exists(fileToTest) Then Dts.Variables("ExcelFileExists").Value = True Else Dts.Variables("ExcelFileExists").Value = False End If Dts.TaskResult = ScriptResults.Success End Sub End Class public class ScriptMain { public void Main() {
80
string fileToTest; fileToTest = Dts.Variables["ExcelFile"].Value.ToString(); if (File.Exists(fileToTest)) { Dts.Variables["ExcelFileExists"].Value = true; } else { Dts.Variables["ExcelFileExists"].Value = false; } Dts.TaskResult = (int)ScriptResults.Success; } }
3. Click ReadWriteVariables and enter the property value using one of the following methods: Type ExcelTableExists. -orClick the ellipsis () button next to the property field, and in the Select variables dialog box, select the ExcelTableExists variable. 81
5. Add a reference to the System.Xml assembly in the script project. 6. Add Imports statements for the System.IO and System.Data.OleDb namespaces at the top of the script file. 7. Add the following code.
Example 2 Code
Public Class ScriptMain Public Sub Main() Dim fileToTest As String Dim tableToTest As String Dim connectionString As String Dim excelConnection As OleDbConnection Dim excelTables As DataTable Dim excelTable As DataRow Dim currentTable As String fileToTest = Dts.Variables("ExcelFile").Value.ToString tableToTest = Dts.Variables("ExcelTable").Value.ToString Dts.Variables("ExcelTableExists").Value = False If File.Exists(fileToTest) Then connectionString = "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=" & fileToTest & _ ";Extended Properties=Excel 8.0" excelConnection = New OleDbConnection(connectionString) excelConnection.Open() excelTables = excelConnection.GetSchema("Tables") For Each excelTable In excelTables.Rows currentTable = excelTable.Item("TABLE_NAME").ToString If currentTable = tableToTest Then Dts.Variables("ExcelTableExists").Value = True End If Next End If
82
Dts.TaskResult = ScriptResults.Success End Sub End Class public class ScriptMain { public void Main() { string fileToTest; string tableToTest; string connectionString; OleDbConnection excelConnection; DataTable excelTables; string currentTable; fileToTest = Dts.Variables["ExcelFile"].Value.ToString(); tableToTest = Dts.Variables["ExcelTable"].Value.ToString(); Dts.Variables["ExcelTableExists"].Value = false; if (File.Exists(fileToTest)) { connectionString = "Provider=Microsoft.Jet.OLEDB.4.0;" + "Data Source=" + fileToTest + ";Extended Properties=Excel 8.0"; excelConnection = new OleDbConnection(connectionString); excelConnection.Open(); excelTables = excelConnection.GetSchema("Tables"); foreach (DataRow excelTable in excelTables.Rows) { currentTable = excelTable["TABLE_NAME"].ToString(); if (currentTable == tableToTest) { Dts.Variables["ExcelTableExists"].Value = true; } }
83
} Dts.TaskResult = (int)ScriptResults.Success; } }
3. Click ReadWriteVariables and enter the property value using one of the following methods: Type ExcelFiles. -orClick the ellipsis () button next to the property field, and in the Select variables dialog box, select the ExcelFiles variable.
4. Click Edit Script to open the script editor. 5. Add an Imports statement for the System.IO namespace at the top of the script file. 6. Add the following code.
Example 3 Code
Public Class ScriptMain Public Sub Main() Const FILE_PATTERN As String = "*.xls" Dim excelFolder As String Dim excelFiles As String()
84
excelFolder = Dts.Variables("ExcelFolder").Value.ToString excelFiles = Directory.GetFiles(excelFolder, FILE_PATTERN) Dts.Variables("ExcelFiles").Value = excelFiles Dts.TaskResult = ScriptResults.Success End Sub End Class public class ScriptMain { public void Main() { const string FILE_PATTERN = "*.xls"; string excelFolder; string[] excelFiles; excelFolder = Dts.Variables["ExcelFolder"].Value.ToString(); excelFiles = Directory.GetFiles(excelFolder, FILE_PATTERN); Dts.Variables["ExcelFiles"].Value = excelFiles; Dts.TaskResult = (int)ScriptResults.Success; } }
Alternate Solution
Instead of using a Script task to gather a list of Excel files into an array, you can also use the ForEach File enumerator to iterate over all the Excel files in a folder. For more information, see How to: Loop through Excel Files and Tables.
Note The list of tables in an Excel workbook includes both worksheets (which have the $ suffix) and named ranges. If you have to filter the list for only worksheets or only named ranges, you may have to add additional code for this purpose. To configure this Script Task example 1. Add a new Script task to the package and change its name to GetExcelTables. 2. Open the Script Task Editor, on the Script tab, click ReadOnlyVariables and enter the property value using one of the following methods: Type ExcelFile. -orClick the ellipsis () button next to the property field, and in the Select variables dialog box, select the ExcelFile variable.
3. Click ReadWriteVariables and enter the property value using one of the following methods: Type ExcelTables. -orClick the ellipsis () button next to the property field, and in the Select variables dialog box, select the ExcelTables variable.
4. Click Edit Script to open the script editor. 5. Add a reference to the System.Xml namespace in the script project. 6. Add an Imports statement for the System.Data.OleDb namespace at the top of the script file. 7. Add the following code.
Example 4 Code
Public Class ScriptMain Public Sub Main() Dim excelFile As String Dim connectionString As String Dim excelConnection As OleDbConnection Dim tablesInFile As DataTable Dim tableCount As Integer = 0 Dim tableInFile As DataRow Dim currentTable As String Dim tableIndex As Integer = 0
86
Dim excelTables As String() excelFile = Dts.Variables("ExcelFile").Value.ToString connectionString = "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=" & excelFile & _ ";Extended Properties=Excel 8.0" excelConnection = New OleDbConnection(connectionString) excelConnection.Open() tablesInFile = excelConnection.GetSchema("Tables") tableCount = tablesInFile.Rows.Count ReDim excelTables(tableCount - 1) For Each tableInFile In tablesInFile.Rows currentTable = tableInFile.Item("TABLE_NAME").ToString excelTables(tableIndex) = currentTable tableIndex += 1 Next Dts.Variables("ExcelTables").Value = excelTables Dts.TaskResult = ScriptResults.Success End Sub End Class public class ScriptMain { public void Main() { string excelFile; string connectionString; OleDbConnection excelConnection; DataTable tablesInFile; int tableCount = 0; string currentTable; int tableIndex = 0;
87
string[] excelTables = new string[5]; excelFile = Dts.Variables["ExcelFile"].Value.ToString(); connectionString = "Provider=Microsoft.Jet.OLEDB.4.0;" + "Data Source=" + excelFile + ";Extended Properties=Excel 8.0"; excelConnection = new OleDbConnection(connectionString); excelConnection.Open(); tablesInFile = excelConnection.GetSchema("Tables"); tableCount = tablesInFile.Rows.Count; foreach (DataRow tableInFile in tablesInFile.Rows) { currentTable = tableInFile["TABLE_NAME"].ToString(); excelTables[tableIndex] = currentTable; tableIndex += 1; } Dts.Variables["ExcelTables"].Value = excelTables; Dts.TaskResult = (int)ScriptResults.Success; } }
Alternate Solution
Instead of using a Script task to gather a list of Excel tables into an array, you can also use the ForEach ADO.NET Schema Rowset Enumerator to iterate over all the tables (that is, worksheets and named ranges) in an Excel workbook file. For more information, see How to: Loop through Excel Files and Tables.
2. Connect each of the four example Script tasks to one another, so that each task runs after the preceding task completes successfully, and connect the fourth example task to the DisplayResults task. 3. Open the DisplayResults task in the Script Task Editor. 4. On the Script tab, click ReadOnlyVariables and use one of the following methods to add all seven variables listed in Configuring a Package to Test the Samples: Type the name of each variable separated by commas. -orClick the ellipsis () button next to the property field, and in the Select variables dialog box, selecting the variables.
5. Click Edit Script to open the script editor. 6. Add Imports statements for the Microsoft.VisualBasic and System.Windows.Forms namespaces at the top of the script file. 7. Add the following code. 8. Run the package and examine the results displayed in a message box.
89
EOL results &= "Excel files in folder: " & EOL filesInFolder = DirectCast(Dts.Variables("ExcelFiles").Value, String()) For Each fileInFolder In filesInFolder results &= " " & fileInFolder & EOL Next results &= EOL results &= "Excel tables in file: " & EOL tablesInFile = DirectCast(Dts.Variables("ExcelTables").Value, String()) For Each tableInFile In tablesInFile results &= " " & tableInFile & EOL Next MessageBox.Show(results, "Results", MessageBoxButtons.OK, MessageBoxIcon.Information) Dts.TaskResult = ScriptResults.Success End Sub End Class public class ScriptMain { public void Main() { const string EOL = "\r"; string results; string[] filesInFolder; //string fileInFolder; string[] tablesInFile; //string tableInFile;
90
results = "Final values of variables:" + EOL + "ExcelFile: " + Dts.Variables["ExcelFile"].Value.ToString() + EOL + "ExcelFileExists: " + Dts.Variables["ExcelFileExists"].Value.ToString() + EOL + "ExcelTable: " + Dts.Variables["ExcelTable"].Value.ToString() + EOL + "ExcelTableExists: " + Dts.Variables["ExcelTableExists"].Value.ToString() + EOL + "ExcelFolder: " + Dts.Variables["ExcelFolder"].Value.ToString() + EOL + EOL; results += "Excel files in folder: " + EOL; filesInFolder = (string[])(Dts.Variables["ExcelFiles"].Value); foreach (string fileInFolder in filesInFolder) { results += " " + fileInFolder + EOL; } results += EOL; results += "Excel tables in file: " + EOL; tablesInFile = (string[])(Dts.Variables["ExcelTables"].Value); foreach (string tableInFile in tablesInFile) { results += " " + tableInFile + EOL; } MessageBox.Show(results, "Results", MessageBoxButtons.OK, MessageBoxIcon.Information); Dts.TaskResult = (int)ScriptResults.Success; } }
91
Stay Up to Date with Integration Services For the latest downloads, articles, samples, and videos from Microsoft, as well as selected solutions from the community, visit the Integration Services page on MSDN:
See Also
Excel Connection Manager How to: Loop through Excel Files and Tables
Description
The following example uses an existing MSMQ connection manager, together with objects and methods from the System.Messaging namespace, to send the text contained in a package variable to a remote private message queue. The call to the M:Microsoft.SqlServer.Dts.ManagedConnections.MSMQConn.AcquireConnection(System. Object) method of the MSMQ connection manager returns a MessageQueue object whose Send method accomplishes this task. 92
To configure this Script Task example 1. Create an MSMQ connection manager with the default name. Set the path of a valid remote private queue, in the following format:
FORMATNAME:DIRECT=OS:<computername>\private$\<queuename>
2. Create an Integration Services variable named MessageText of type String to pass the message text into the script. Enter a default message as the value of the variable. 3. Add a Script Task to the design surface and edit it. On the Script tab of the Script Task Editor, add the MessageText variable to the ReadOnlyVariables property to make the variable available inside the script. 4. Click Edit Script to open the Microsoft Visual Studio Tools for Applications (VSTA) script editor. 5. Add a reference in the script project to the System.Messaging namespace. 6. Replace the contents of the script window with the code in the following section.
Code
Imports System Imports Microsoft.SqlServer.Dts.Runtime Imports System.Messaging Public Class ScriptMain Public Sub Main() Dim remotePrivateQueue As MessageQueue Dim messageText As String remotePrivateQueue = _ DirectCast(Dts.Connections("Message Queue Connection Manager").AcquireConnection(Dts.Transaction), _ MessageQueue) messageText = DirectCast(Dts.Variables("MessageText").Value, String) remotePrivateQueue.Send(messageText) Dts.TaskResult = ScriptResults.Success
93
End Sub End Class using System; using Microsoft.SqlServer.Dts.Runtime; using System.Messaging; public class ScriptMain { public void Main() { MessageQueue remotePrivateQueue = new MessageQueue(); string messageText;
remotePrivateQueue = (MessageQueue)(Dts.Connections["Message Queue Connection Manager"].AcquireConnection(Dts.Transaction) as MessageQueue); messageText = (string)(Dts.Variables["MessageText"].Value); remotePrivateQueue.Send(messageText); Dts.TaskResult = (int)ScriptResults.Success; } }
94
Stay Up to Date with Integration Services For the latest downloads, articles, samples, and videos from Microsoft, as well as selected solutions from the community, visit the Integration Services page on MSDN:
See Also
Message Queue Task
95
The Script component and the infrastructure code that it generates for you simplify significantly the process of developing a custom data flow component. However, to understand how the Script component works, you may find it useful to read the section Extending the Data Flow with Custom Components to understand the steps that are involved in developing a custom data flow component. If you are creating a source, transformation, or destination that you plan to reuse in multiple packages, you should consider developing a custom component instead of using the Script component. For more information, see Extending the Data Flow Task with Custom Components.
In This Section
The following topics provide more information about the Script component. Configuring the Script Component
Properties that you configure in the Script Transformation Editor affect the capabilities and the performance of Script component code.
96
Stay Up to Date with Integration Services For the latest downloads, articles, samples, and videos from Microsoft, as well as selected solutions from the community, visit the Integration Services page on MSDN:
See Also
Script Component Comparing the Script Task and the Script Component
When you open the Script Transformation Editor, the component enters metadata design mode. In this mode, you can select input columns, and add or configure outputs and output columns, but you cannot write code. After you have configured the component's metadata, you can switch to code design mode to write the script. When you switch to code design mode by clicking Edit Script, the Script component locks metadata to prevent additional changes, and then automatically generates base code from the metadata of the inputs and outputs. After the autogenerated code is complete, you will be able to enter your custom code. Your code uses the auto-generated base classes to process input rows, to access buffers and columns in the buffers, and to retrieve connection managers and variables from the package, all as strongly-typed objects. After entering your custom code in code design mode, you can switch back to metadata design mode. This does not delete any code that you have entered; however, subsequent changes to the metadata cause the base class to be regenerated. Afterward, your component may fail validation because objects referenced by your custom code may no longer exist or may have been modified. In this case, you must fix your code manually so that it can be compiled successfully against the regenerated base class.
98
For more information about how to configure input columns for a specific type of data flow component, see the appropriate example under Developing Specific Types of Script Components.
In the code project that will be generated based on this metadata, the BufferWrapper project item contains a class for each input and output. For example, if you create an output named CustomerOutput, the BufferWrapper project item will contain a CustomerOutput class that derives from T:Microsoft.SqlServer.Dts.Pipeline.ScriptBuffer, and the CustomerOutput class will contain typed accessor properties for each output column created. You can configure output columns only on the Input and Outputs page. You can select input columns for transformations and destinations on the Input Columns page. The typed accessor properties created for you in the BufferWrapper project item will be write-only for output columns. The accessor properties for input columns will be read-only or read/write depending on the usage type that you have selected for each column on the Input Columns page. For more information about configuring inputs and outputs for a specific type of data flow component see the appropriate example under Developing Specific Types of Script Components. Note Although you cannot directly configure an output as an error output in the Script component for automatic handling of error rows, you can reproduce the functionality of an error output by creating an additional output and using script to direct rows to this output when appropriate. For more information, see Simulating an Error Output in the Script Component.
asynchronous. For a synchronous output, where rows are passed through to the selected output or outputs without adding any new rows, this property should contain the ID of the component's input. Note When the Script Transformation Editor creates the first output, the editor sets the SynchronousInputID property of the output to the ID of the component's input. However, when the editor creates subsequent outputs, the editor sets the SynchronousInputID properties of those outputs to zero. If you are creating a component with synchronous outputs, each output must have its SynchronousInputID property set to the ID of the components input. Therefore, each output that the editor creates after the first output must have its SynchronousInputID value changed from zero to the ID of the component's input. If you are creating a component with asynchronous outputs, each output must have its SynchronousInputID property set to zero. Therefore, the first output must have its SynchronousInputID value changed from the ID of the components input to zero.
For an example of directing rows to one of two synchronous outputs in the Script component, see Creating a Synchronous Transformation with the Script Component.
ValidateExternalMetadata Property
The Boolean value of the ValidateExternalMetadata property specifies whether the component should perform validation against external data sources at design time, or whether it should 100
postpone validation until run time. By default, the value of this property is True; that is, the external metadata is validated both at design time and at run time. You may want to set the value of this property to False when an external data source is not available at design time: for example, when the package downloads the source or creates the destination only at run time.
ScriptLanguage
You can select either Microsoft Visual Basic or Microsoft Visual C# as the programming language for the Script component.
For more information, see Connecting to Data Sources in the Script Component.
101
Stay Up to Date with Integration Services For the latest downloads, articles, samples, and videos from Microsoft, as well as selected solutions from the community, visit the Integration Services page on MSDN:
See Also
Coding and Debugging the Script Component
Writing the Script in Code Design Mode Script Component Development Environment
To write your script, click Edit Script on the Script page of the Script Transformation Editor to open the Microsoft Visual Studio Tools for Applications (VSTA) IDE. The VSTA IDE includes all the standard features of the Visual Studio .NET environment, such as the color-coded Visual Studio editor, IntelliSense, and Object Browser. Script code is written in Microsoft Visual Basic or Microsoft Visual C#. You specify the script language by setting the ScriptLanguage property in the Script Transformation Editor. If you prefer to use another programming language, you can develop a custom assembly in your language of choice and call its functionality from the code in the Script component. 102
The script that you create in the Script component is stored in the package definition. There is no separate script file. Therefore, the use of the Script component does not affect package deployment. Note While you design the package, the script code is temporarily written to a project file. Because storing sensitive information in a file is a potential security risk, we recommended that you do not include sensitive information such as passwords in the script code. By default, Option Strict is disabled in the IDE.
properties that you will use to process data and to interact with the package. The ScriptMain class inherits from the UserComponent class.
A Connections collection class that contains references to the connections selected on the Connection Manager page of the Script Transformation Editor. A Variables collection class that contains references to the variables entered in the ReadOnlyVariable and ReadWriteVariables properties on the Script page of the Script Transformation Editor.
Inputs and Outputs page of the Script Transformation Editor. Each of these classes contains typed accessor properties that correspond to the configured input and output columns, and the data flow buffers that contain the columns.
For information about how to use these objects, methods, and properties, see Using the Script Component Object Model. For information about how to use the methods and properties of 103
these classes in a particular type of Script component, see the section Examples of Specific Types of Script Components. The example topics also contain complete code samples. When you configure the Script component as a transformation, the ScriptMain project item contains the following autogenerated code. The code template also provides an overview of the Script component, and additional information on how to retrieve and manipulate SSIS objects, such as variables, events, and connections.
' Microsoft SQL Server Integration Services Script Component ' Write scripts using Microsoft Visual Basic 2008. ' ScriptMain is the entry point class of the script. Imports System Imports System.Data Imports System.Math Imports Microsoft.SqlServer.Dts.Pipeline.Wrapper Imports Microsoft.SqlServer.Dts.Runtime.Wrapper <Microsoft.SqlServer.Dts.Pipeline.SSISScriptComponentEntryPointAttribute> _ <CLSCompliant(False)> _ Public Class ScriptMain Inherits UserComponent Public Overrides Sub PreExecute() MyBase.PreExecute() ' ' Add your code here for preprocessing or remove if not needed ' End Sub Public Overrides Sub PostExecute() MyBase.PostExecute() ' ' Add your code here for postprocessing or remove if not needed ' You can set read/write variables here, for example: ' Me.Variables.MyIntVar = 100
104
' End Sub Public Overrides Sub Input0_ProcessInputRow(ByVal Row As Input0Buffer) ' ' Add your code here ' End Sub End Class /* Microsoft SQL Server Integration Services user script component * * Write scripts using Microsoft Visual C# 2008. ScriptMain is the entry point class of the script.*/
using System; using System.Data; using Microsoft.SqlServer.Dts.Pipeline.Wrapper; using Microsoft.SqlServer.Dts.Runtime.Wrapper; [Microsoft.SqlServer.Dts.Pipeline.SSISScriptComponentEntryPointAttribute] public class ScriptMain : UserComponent { public override void PreExecute() { base.PreExecute(); /* Add your code here for preprocessing or remove if not needed */ } public override void PostExecute() {
105
base.PostExecute(); /* Add your code here for postprocessing or remove if not needed You can set read/write variables here, for example: Variables.MyIntVar = 100 */ } public override void Input0_ProcessInputRow(Input0Buffer Row) { /* Add your code here */ } }
The autogenerated project items in the Script component project provide the following objects, methods, and properties for interacting with the package.
Package Feature Access Method
Variables
Use the named and typed accessor properties in the Variables collection class in the ComponentWrapper project item, exposed through the Variables property of the ScriptMain class. The PreExecute method can access only read-only variables. The PostExecute method can access both read-only and read/write variables.
Connectio ns Events
Use the named and typed accessor properties in the Connections collection class in the ComponentWrapper project item, exposed through the Connections property of the ScriptMain class. Raise events by using the
P:Microsoft.SqlServer.Dts.Pipeline.ScriptComponent.ComponentMetaData property T:Microsoft.SqlServer.Dts.Pipeline.Wrapper.IDTSComponentMetaData100 interface.
Logging
Interrupt execution and display a modal message by using the MessageBox.Show method in the System.Windows.Forms namespace. (Remove this code after you complete the debugging process.) Raise events for informational messages, warnings, and errors. The FireInformation, FireWarning, and FireError methods display the event description in the Visual Studio Output window. However, the FireProgress method, the Console.Write method, and Console.WriteLine method do not display any information in the Output window. Messages from the FireProgress event appear on the Progress tab of SSIS Designer. For more information, see Raising Events in the Script Component. Log events or user-defined messages to enabled logging providers. For more information, see Logging in the Script Component.
If you just want to examine the output of a Script component configured as a source or as a transformation, without saving the data to a destination, you can stop the data flow with a Row Count Transformation and attach a data viewer to the output of the Script component. For information about data viewers, see Debugging Data Flow.
In This Section
For more information about coding the Script component, see the following topics in this section. Using the Script Component Object Model
Explains how to use the objects, methods, and properties available in the Script component.
External Resources
Blog entry, VSTA setup and configuration troubles for SSIS 2008 and R2 installations, on blogs.msdn.com.
108
Stay Up to Date with Integration Services For the latest downloads, articles, samples, and videos from Microsoft, as well as selected solutions from the community, visit the Integration Services page on MSDN:
For automatic notification of these updates, subscribe to the RSS feeds available on the page.
See Also
Configuring the Script Component in the Script Component Editor
For information about how to use the methods and properties of these classes in a particular type of Script component, see the section Examples of Specific Types of Script Components. The example topics also contain complete code samples.
AcquireConnections Method
Sources and destinations generally must connect to an external data source. Override the M:Microsoft.SqlServer.Dts.Pipeline.ScriptComponent.AcquireConnections(System.Object) method of the T:Microsoft.SqlServer.Dts.Pipeline.ScriptComponent base class to retrieve the connection or the connection information from the appropriate connection manager. The following example returns a System.Data.SqlClient.SqlConnection from an ADO.NET connection manager.
Dim connMgr As IDTSConnectionManager100 Dim sqlConn As SqlConnection Public Overrides Sub AcquireConnections(ByVal Transaction As Object) connMgr = Me.Connections.MyADONETConnection sqlConn = CType(connMgr.AcquireConnection(Nothing), SqlConnection) End Sub
The following example returns a complete path and file name from a Flat File Connection Manager, and then opens the file by using a System.IO.StreamReader.
Private textReader As StreamReader Public Overrides Sub AcquireConnections(ByVal Transaction As Object) Dim connMgr As IDTSConnectionManager100 = _ Me.Connections.MyFlatFileSrcConnectionManager Dim exportedAddressFile As String = _ CType(connMgr.AcquireConnection(Nothing), String) textReader = New StreamReader(exportedAddressFile) End Sub
PreExecute Method
Override the M:Microsoft.SqlServer.Dts.Pipeline.ScriptComponent.PreExecute method of the T:Microsoft.SqlServer.Dts.Pipeline.ScriptComponent base class whenever you have processing that you must perform one time only before you start processing rows of data. For example, in a 110
destination, you may want to configure the parameterized command that the destination will use to insert each row of data into the data source.
Dim sqlConn As SqlConnection Dim sqlCmd As SqlCommand Dim sqlParam As SqlParameter ... Public Overrides Sub PreExecute() sqlCmd = New SqlCommand("INSERT INTO Person.Address2(AddressID, City) " & _ "VALUES(@addressid, @city)", sqlConn) sqlParam = New SqlParameter("@addressid", SqlDbType.Int) sqlCmd.Parameters.Add(sqlParam) sqlParam = New SqlParameter("@city", SqlDbType.NVarChar, 30) sqlCmd.Parameters.Add(sqlParam) End Sub SqlConnection sqlConn; SqlCommand sqlCmd; SqlParameter sqlParam; public override void PreExecute() { sqlCmd = new SqlCommand("INSERT INTO Person.Address2(AddressID, City) " + "VALUES(@addressid, @city)", sqlConn); sqlParam = new SqlParameter("@addressid", SqlDbType.Int); sqlCmd.Parameters.Add(sqlParam); sqlParam = new SqlParameter("@city", SqlDbType.NVarChar, 30); sqlCmd.Parameters.Add(sqlParam); }
Script components that are configured as transformations or destinations have one input.
Override <inputbuffer>_ProcessInput only if you have to do something additional while looping through input rows. (For example, you have to test for EndOfRowset to take some other action after all rows have been processed.) Call <inputbuffer>_ProcessInputRow to perform the row processing. Override FinishOutputs if you have to do something to the outputs before they are closed.
The ProcessInput method ensures that these methods are called at the appropriate times.
Processing Outputs
Script components configured as sources or transformations have one or more outputs.
a transformation with asynchronous outputs, you should call AddRow during or after the processing of input data. Override FinishOutputs if you have to do something to the outputs before they are closed. The PrimeOutput method ensures that these methods are called at the appropriate times.
PostExecute Method
Override the M:Microsoft.SqlServer.Dts.Pipeline.ScriptComponent.PostExecute method of the T:Microsoft.SqlServer.Dts.Pipeline.ScriptComponent base class whenever you have processing that you must perform one time only after you have processed the rows of data. For example, in a source, you may want to close the System.Data.SqlClient.SqlDataReader that you have used to load data into the data flow. Important The collection of ReadWriteVariables is available only in the PostExecute method. Therefore you cannot directly increment the value of a package variable as you process each row of data. Instead, increment the value of a local variable, and set the value of the package variable to the value of the local variable in the PostExecute method after all data has been processed.
ReleaseConnections Method
Sources and destinations typically must connect to an external data source. Override the M:Microsoft.SqlServer.Dts.Pipeline.ScriptComponent.ReleaseConnections method of the T:Microsoft.SqlServer.Dts.Pipeline.ScriptComponent base class to close and release the connection that you have opened previously in the M:Microsoft.SqlServer.Dts.Pipeline.ScriptComponent.AcquireConnections(System.Object) method.
Dim connMgr As IDTSConnectionManager100 ... Public Overrides Sub ReleaseConnections() connMgr.ReleaseConnection(sqlConn) End Sub IDTSConnectionManager100 connMgr; public override void ReleaseConnections() { connMgr.ReleaseConnection(sqlConn);
114
Stay Up to Date with Integration Services For the latest downloads, articles, samples, and videos from Microsoft, as well as selected solutions from the community, visit the Integration Services page on MSDN:
See Also
Configuring the Script Component in the Script Component Editor Coding the Script Component
P:Microsoft.SqlServer.Dts.Pipeline.ScriptComponent.VariableDispenser property to work around this limitation, as described later in this topic. However, writing directly to a package variable as each row is processed will negatively impact performance and increase the risk of locking conflicts.
For more information about the Script page of the Script Transformation Editor, see Configuring the Script Component and Script Transformation Editor (Script Page). The Script component creates a Variables collection class in the ComponentWrapper project item with a strongly-typed accessor property for the value of each preconfigured variable where the property has the same name as the variable itself. This collection is exposed through the Variables property of the ScriptMain class. The accessor property provides read-only or read/write permission to the value of the variable as appropriate. For example, if you have added an integer variable named MyIntegerVariable to the ReadOnlyVariables list, you can retrieve its value in your script by using the following code:
Dim myIntegerVariableValue As Integer = Me.Variables.MyIntegerVariable
You can also use the P:Microsoft.SqlServer.Dts.Pipeline.ScriptComponent.VariableDispenser property, accessed by calling Me.VariableDispenser, to work with variables in the Script component. In this case you are not using the typed and named accessor properties for variables, but accessing the variables directly. When using the P:Microsoft.SqlServer.Dts.Pipeline.ScriptComponent.VariableDispenser, you must handle both the locking semantics and the casting of data types for variable values in your own code. You have to use the P:Microsoft.SqlServer.Dts.Pipeline.ScriptComponent.VariableDispenser property instead of the named and typed accessor properties if you want to work with a variable that is not available at design time but is created programmatically at run time.
Stay Up to Date with Integration Services For the latest downloads, articles, samples, and videos from Microsoft, as well as selected solutions from the community, visit the Integration Services page on MSDN:
116
See Also
Integration Services Variables Using Variables in Packages
Note You must know the type of connection that is returned by the connection manager before you call AcquireConnection. Because the Script task has Option Strict enabled, you must cast the connection, which is returned as type Object, to the appropriate connection type before you can use it. Next, you call the AcquireConnection method of the specific connection manager to obtain either the underlying connection or the information that is required to connect to the data source. For example, you obtain a reference to the System.Data.SqlConnection wrapped by an ADO.NET connection manager by using the following code:
Dim myADOConnection As SqlConnection = _ CType(MyADONETConnectionManager.AcquireConnection(Nothing), SqlConnection)
In contrast, the same call to a flat file connection manager returns only the path and file name of the file data source. 117
You then must provide this path and file name to a System.IO.StreamReader or Streamwriter to read or write the data in the flat file. Important When you write managed code in a Script component, you cannot call the AcquireConnection method of connection managers that return unmanaged objects, such as the OLE DB connection manager and the Excel connection manager. However, you can read the ConnectionString property of these connection managers, and connect to the data source directly in your code by using the connection string of an OLEDB connection from the System.Data.OleDb namespace. If you need to call the AcquireConnection method of a connection manager that returns an unmanaged object, use an ADO.NET connection manager. When you configure the ADO.NET connection manager to use an OLE DB provider, it connects by using the .NET Framework Data Provider for OLE DB. In this case, the AcquireConnection method returns a System.Data.OleDb.OleDbConnection instead of an unmanaged object. To configure an ADO.NET connection manager for use with an Excel data source, select the Microsoft OLE DB Provider for Jet, specify an Excel workbook, and then enter Excel 8.0 (for Excel 97 and later) as the value of Extended Properties on the All page of the Connection Manager dialog box.
For more information about how to use connection managers with the script component, see Creating a Source with the Script Component and Creating a Destination with the Script Component.
Stay Up to Date with Integration Services For the latest downloads, articles, samples, and videos from Microsoft, as well as selected solutions from the community, visit the Integration Services page on MSDN:
118
See Also
Integration Services Connections Creating Connection Managers
To raise an event, the Script task calls one of the following methods of the T:Microsoft.SqlServer.Dts.Pipeline.Wrapper.IDTSComponentMetaData100 interface exposed by the P:Microsoft.SqlServer.Dts.Pipeline.ScriptComponent.ComponentMetaData property:
Event Descri ption
M:Microsoft.SqlServer.Dts.Pipeline.Wrapper.IDTSComponentMetaData100.FireError(Syste m.Int32,System.String,System.String,System.String,System.Int32,System.Boolean@)
Event
Descri ption
Provi des infor matio n to the user. Infor ms the packa ge of the progr ess of the comp onent . Infor ms the packa ge that the comp onent is in a state that warra nts user 120
M:Microsoft.SqlServer.Dts.Pipeline.Wrapper.IDTSComponentMetaData100.FireProgress(Sy stem.String,System.Int32,System.Int32,System.Int32,System.String,System.Boolean@)
M:Microsoft.SqlServer.Dts.Pipeline.Wrapper.IDTSComponentMetaData100.FireWarning(Sy stem.Int32,System.String,System.String,System.String,System.Int32)
Event
Descri ption
notifi catio n, but is not an error condi tion. Here is a simple example of raising an Error event:
Dim myMetadata as IDTSComponentMetaData100 myMetaData = Me.ComponentMetaData myMetaData.FireError(...)
Stay Up to Date with Integration Services For the latest downloads, articles, samples, and videos from Microsoft, as well as selected solutions from the community, visit the Integration Services page on MSDN:
See Also
Integration Services Event Handlers Add an Event Handler to a Package
121
ScriptComponentLogEntry event is selected for logging on the Details tab of the Configure SSIS Logs dialog box, a single call to the
M:Microsoft.SqlServer.Dts.Pipeline.ScriptComponent.Log(System.String,System.Int32,System.Byte[ ]) method stores the event information in all the log providers that have been configured for the
Note Although you can perform logging directly from your Script component, you may want to consider implementing events rather than logging. When using events, not only can you enable the logging of event messages, but you can respond to the event with default or user-defined event handlers. For more information about logging, see Integration Services Logging.
122
Stay Up to Date with Integration Services For the latest downloads, articles, samples, and videos from Microsoft, as well as selected solutions from the community, visit the Integration Services page on MSDN:
See Also
Integration Services Logging
For additional examples of the Script component, see Script Component Examples.
In This Section
Creating a Source with the Script Component
Explains and demonstrates how to create a data flow source by using the Script component.
123
Stay Up to Date with Integration Services For the latest downloads, articles, samples, and videos from Microsoft, as well as selected solutions from the community, visit the Integration Services page on MSDN:
See Also
Comparing Scripting Solutions and Custom Objects Developing Specific Types of Data Flow Components
developing a custom data flow component. See the section Extending the Data Flow with Custom Components, especially the topic Creating a Source Component.
will refer to the output columns in your script by the names that you assign here, by using the typed accessor properties created for you in the auto-generated code. You may want to create one or more additional outputs, such as a simulated error output for rows that contain unexpected values. Use the Add Output and Remove Output buttons to manage the outputs of the source component. All input rows are directed to all available outputs unless you also specify an identical non-zero value for the ExclusionGroup property of those outputs where you intend to direct each row to only one of the outputs that share the same ExclusionGroup value. The particular integer value selected to identify the ExclusionGroup is not significant. Note You can also use a non-zero ExclusionGroup property value with a single output when you do not want to output all rows. In this case, however, you must explicitly call the DirectRowTo<outputbuffer> method for each row that you want to send to the output. You may want to assign a friendly name to the outputs. Later, you will refer to the outputs by their names in your script, by using the typed accessor properties created for you in the auto-generated code. Ordinarily multiple outputs in the same ExclusionGroup have the same output columns. However, if you are creating a simulated error output, you may want to add more columns to store error information. For information about how the data flow engine processes error rows, see Creating and Using Error Outputs. In the Script component, however, you must write your own code to fill the additional columns with appropriate error information. For more information, see Simulating an Error Output for the Script Component.
For more information about the Inputs and Outputs page of the Script Transformation Editor, see Script Transformation Editor (Inputs and Outputs Page).
Adding Variables
If there are any existing variables whose values you want to use in your script, you can add them in the ReadOnlyVariables and ReadWriteVariables property fields on the Script page of the Script Transformation Editor. When you enter multiple variables in the property fields, separate the variable names by commas. You can also enter multiple variables by clicking the ellipsis () button next to the ReadOnlyVariables and ReadWriteVariables property fields and selecting variables in the Select variables dialog box. For general information about how to use variables with the Script component, see Using Variables in the Script Component. For more information about the Script page of the Script Transformation Editor, see Script Transformation Editor (Script Page).
the Script page of the Script Transformation Editor. You can write your script by using either Microsoft Visual Basic or Microsoft Visual C#, depending on the script language selected for the ScriptLanguage property. For important information that applies to all kinds of components created by using the Script component, see Coding the Script Component.
1. The CreateNewOutputRows method, which you override in ScriptMain to add rows from the data source to the output buffers, which are empty at first. 2. The FinishOutputs method, which is empty by default. Override this method in ScriptMain to perform any processing that is required to complete the output. 3. The private MarkOutputsAsFinished method, which calls the
M:Microsoft.SqlServer.Dts.Pipeline.ScriptBuffer.SetEndOfRowset method of the T:Microsoft.SqlServer.Dts.Pipeline.ScriptBuffer parent class to indicate to the data flow
engine that the output is finished. You do not have to call SetEndOfRowset explicitly in your own code.
reading a text file), you can load the data as you loop through rows in CreateNewOutputRows. 3. Use the overridden CreateNewOutputRows method to add new rows to the empty output buffers and to fill in the values of each column in the new output rows. Use the AddRow method of each output buffer to add an empty new row, and then set the values of each column. Typically you copy values from the columns loaded from the external source. 4. Override the PostExecute method to finish processing the data. For example, you can close the SqlDataReader that you used to load data. 5. Override the ReleaseConnections method to disconnect from the external data source, if required.
Examples
The following examples demonstrate the custom code that is required in the ScriptMain class to create a source component. Note These examples use the Person.Address table in the AdventureWorks sample database and pass its first and fourth columns, the int AddressID and nvarchar(30) City columns, through the data flow. The same data is used in the source, transformation, and destination samples in this section. Additional prerequisites and assumptions are documented for each example.
Component, that expects the AddressID and City columns. Then connect the source component to the destination. (You can connect a source directly to a destination without any transformations.) You can create a destination table by running the following TransactSQL command in the AdventureWorks database:
CREATE TABLE [Person].[Address2]( [AddressID] [int] NOT NULL, [City] [nvarchar](30) NOT NULL )
129
Public Overrides Sub CreateNewOutputRows() Do While sqlReader.Read With MyAddressOutputBuffer .AddRow() .AddressID = sqlReader.GetInt32(0) .City = sqlReader.GetString(1) End With Loop End Sub Public Overrides Sub PostExecute() sqlReader.Close() End Sub Public Overrides Sub ReleaseConnections() connMgr.ReleaseConnection(sqlConn) End Sub End Class using System.Data.SqlClient; public class ScriptMain: UserComponent { IDTSConnectionManager100 connMgr; SqlConnection sqlConn;
130
SqlDataReader sqlReader; public override void AcquireConnections(object Transaction) { connMgr = this.Connections.MyADONETConnection; sqlConn = (SqlConnection)connMgr.AcquireConnection(null); } public override void PreExecute() { SqlCommand cmd = new SqlCommand("SELECT AddressID, City, StateProvinceID FROM Person.Address", sqlConn); sqlReader = cmd.ExecuteReader(); } public override void CreateNewOutputRows() { while (sqlReader.Read()) { { MyAddressOutputBuffer.AddRow(); MyAddressOutputBuffer.AddressID = sqlReader.GetInt32(0); MyAddressOutputBuffer.City = sqlReader.GetString(1); } } } public override void PostExecute()
131
CREATE TABLE [Person].[Address2]( [AddressID] [int] NOT NULL, [City] [nvarchar](30) NOT NULL )
133
delimiters = ",".ToCharArray nextLine = textReader.ReadLine Do While nextLine IsNot Nothing columns = nextLine.Split(delimiters) With MyAddressOutputBuffer .AddRow() .AddressID = columns(0) .City = columns(3) End With nextLine = textReader.ReadLine Loop End Sub Public Overrides Sub PostExecute() MyBase.PostExecute() textReader.Close() End Sub End Class using System.IO; public class ScriptMain: UserComponent { private StreamReader textReader; private string exportedAddressFile; public override void AcquireConnections(object Transaction) {
134
IDTSConnectionManager100 connMgr = this.Connections.MyFlatFileSrcConnectionManager; exportedAddressFile = (string)connMgr.AcquireConnection(null); } public override void PreExecute() { base.PreExecute(); textReader = new StreamReader(exportedAddressFile); } public override void CreateNewOutputRows() { string nextLine; string[] columns; char[] delimiters; delimiters = ",".ToCharArray(); nextLine = textReader.ReadLine(); while (nextLine != null) { columns = nextLine.Split(delimiters); { MyAddressOutputBuffer.AddRow(); MyAddressOutputBuffer.AddressID = columns[0]; MyAddressOutputBuffer.City = columns[3]; } nextLine = textReader.ReadLine(); }
135
Stay Up to Date with Integration Services For the latest downloads, articles, samples, and videos from Microsoft, as well as selected solutions from the community, visit the Integration Services page on MSDN:
See Also
Creating a Destination with the Script Component Developing a Custom Source Component
its processing. This topic discusses a synchronous transformation. For information about asynchronous transformations, see Creating an Asynchronous Transformation with the Script Component. For more information about the difference between synchronous and asynchronous components, see Understanding Synchronous and Asynchronous Transformations. For an overview of the Script component, see Programming the Script Component. The Script component and the infrastructure code that it generates for you simplify significantly the process of developing a custom data flow component. However, to understand how the Script component works, you may find it useful to read the steps that you must follow in developing a custom data flow component in the section on Extending the Data Flow with Custom Components, and especially Creating a Transformation Component with Synchronous Outputs.
A transformation component supports one or more outputs. On the Inputs and Outputs page of the Script Transformation Editor, you can see that a single output has been created, but the output has no columns. On this page of the editor, you may need or want to configure the following items. Create one or more additional outputs, such as a simulated error output for rows that contain unexpected values. Use the Add Output and Remove Output buttons to manage the outputs of your synchronous transformation component. All input rows are directed to all available outputs unless you indicate that you intend to redirect each row to one output or the other. You indicate that you intend to redirect rows by specifying a non-zero integer value for the ExclusionGroup property on the outputs. The specific integer value entered in ExclusionGroup to identify the outputs is not significant, but you must use the same integer consistently for the specified group of outputs. Note You can also use a non-zero ExclusionGroup property value with a single output when you do not want to output all rows. However, in this case, you must explicitly call the DirectRowTo<outputbuffer> method for each row that you want to send to the output. Assign a more descriptive name to the input and outputs. The Script component uses these names to generate the typed accessor properties that you will use to refer to the input and outputs in your script. Leave columns as is for synchronous transformations. Typically a synchronous transformation does not add columns to the data flow. Data is modified in place in the buffer, and the buffer is passed on to the next component in the data flow. If this is the case, you do not have to add and configure output columns explicitly on the transformation's outputs. The outputs appear in the editor without any explicitly defined columns. Add new columns to simulated error outputs for row-level errors. Ordinarily multiple outputs in the same ExclusionGroup have the same set of output columns. However, if you are creating a simulated error output, you may want to add more columns to contain error information. For information about how the data flow engine processes error rows, see Creating and Using Error Outputs. Note that in the Script component you must write your own code to fill the additional columns with appropriate error information. For more information, see Simulating an Error Output for the Script Component.
For more information about the Inputs and Outputs page of the Script Transformation Editor, see Script Transformation Editor (Inputs and Outputs Page).
Adding Variables
If you want to use existing variables in your script, you can add them in the ReadOnlyVariables and ReadWriteVariables property fields on the Script page of the Script Transformation Editor. When you add multiple variables in the property fields, separate the variable names by commas. You can also select multiple variables by clicking the ellipsis () button next to the 138
ReadOnlyVariables and ReadWriteVariables property fields, and then selecting the variables in the Select variables dialog box. For general information about how to use variables with the Script component, see Using Variables in the Script Component. For more information about the Script page of the Script Transformation Editor, see Script Transformation Editor (Script Page).
To finish creating a custom synchronous transformation component, you use the overridden ProcessInputRow method to transform the data in each row of the input buffer. The data flow engine passes this buffer, when full, to the next component in the data flow. 139
Depending on your requirements, you may also want to write script in the PreExecute and PostExecute methods, available in the ScriptMain class, to perform preliminary or final processing.
In this example, the Script component generates the DirectRowTo<OutputBufferX> methods for you, based on the names of the outputs that you configured. You can use similar code to direct error rows to a simulated error output.
Examples
The examples here demonstrate the custom code that is required in the ScriptMain class to create a synchronous transformation component. Note These examples use the Person.Address table in the AdventureWorks sample database and pass its first and fourth columns, the int AddressID and nvarchar(30) City columns, through the data flow. The same data is used in the source, transformation, and destination samples in this section. Additional prerequisites and assumptions are documented for each example.
This example demonstrates a synchronous transformation component with a single output. This transformation passes through the AddressID column and converts the City column to uppercase. If you want to run this sample code, you must configure the package and the component as follows: 1. Add a new Script component to the Data Flow designer surface and configure it as a transformation. 2. Connect the output of a source or of another transformation to the new transformation component in SSIS Designer. This output should provide data from the Person.Address table of the AdventureWorks sample database that contains the AddressID and City columns. 3. Open the Script Transformation Editor. On the Input Columns page, select the AddressID and City columns. Mark the City column as Read/Write. 4. On the Inputs and Outputs page, rename the input and output with more descriptive names, such as MyAddressInput and MyAddressOutput. Notice that the SynchronousInputID of the output corresponds to the ID of the input. Therefore you do not have to add and configure output columns. 5. On the Script page, click Edit Script and enter the script that follows. Then close the script development environment and the Script Transformation Editor. 6. Create and configure a destination component that expects the AddressID and City columns, such as a SQL Server destination, or the sample destination component demonstrated in Creating a Destination with the Script Component. Then connect the output of the transformation to the destination component. You can create a destination table by running the following Transact-SQL command in the AdventureWorks database:
CREATE TABLE [Person].[Address2]( [AddressID] [int] NOT NULL, [City] [nvarchar](30) NOT NULL )
141
End Class public class ScriptMain: UserComponent { public override void MyAddressInput_ProcessInputRow(MyAddressInputBuffer Row) { Row.City = (Row.City).ToUpper(); } }
142
5. Rename the input and outputs with more descriptive names, such as MyAddressInput, MyRedmondAddresses, and MyOtherAddresses. 6. On the Script page, click Edit Script and enter the script that follows. Then close the script development environment and the Script Transformation Editor. 7. Create and configure two destination components that expect the AddressID and City columns, such as a SQL Server destination, a Flat File destination, or the sample destination component demonstrated in Creating a Destination with the Script Component. Then connect each of the outputs of the transformation to one of the destination components. You can create destination tables by running a Transact-SQL command similar to the following (with unique table names) in the AdventureWorks database:
CREATE TABLE [Person].[Address2]( [AddressID] [int] NOT NULL, [City] [nvarchar](30) NOT NULL
143
Stay Up to Date with Integration Services For the latest downloads, articles, samples, and videos from Microsoft, as well as selected solutions from the community, visit the Integration Services page on MSDN:
See Also
Understanding Synchronous and Asynchronous Outputs Creating an Asynchronous Transformation with the Script Component Developing a Custom Transformation Component with Synchronous Outputs 144
On the Input Columns page of the Script Transformation Editor, the columns list shows the available columns from the output of the upstream component in the data flow. Select the columns that you want to transform or pass through. Mark any columns that you want to transform in place as Read/Write. For more information about the Input Columns page of the Script Transformation Editor, see Script Transformation Editor (Input Columns Page).
For more information about the Inputs and Outputs page of the Script Transformation Editor, see Script Transformation Editor (Inputs and Outputs Page).
Adding Variables
If there are any existing variables whose values you want to use in your script, you can add them in the ReadOnlyVariables and ReadWriteVariables property fields on the Script page of the Script Transformation Editor. 146
When you add multiple variables in the property fields, separate the variable names by commas. You can also select multiple variables by clicking the ellipsis () button next to the ReadOnlyVariables and ReadWriteVariables property fields, and then selecting the variables in the Select variables dialog box. For general information about how to use variables with the Script component, see Using Variables in the Script Component. For more information about the Script page of the Script Transformation Editor, see Script Transformation Editor (Script Page).
Next, the data flow engine invokes the ProcessInput method in the UserComponent class, which overrides the parent class. The ProcessInput method in turn loops through the rows in the input buffer and calls the ProcessInputRow method one time for each row.
M:Microsoft.SqlServer.Dts.Pipeline.ScriptComponent.ProcessInput(System.Int32,Microsoft.SqlServ er.Dts.Pipeline.PipelineBuffer) method of the T:Microsoft.SqlServer.Dts.Pipeline.ScriptComponent
147
To finish creating a custom asynchronous transformation component, you must use the overridden ProcessInputRow method to process the data in each row of the input buffer. Because the outputs are not synchronous to the input, you must explicitly write rows of data to the outputs. In an asynchronous transformation, you can use the AddRow method to add rows to the output as appropriate from within the ProcessInputRow or ProcessInput methods. You do not have to use the CreateNewOutputRows method. If you are writing a single row of results, such as aggregation results, to a particular output, you can create the output row beforehand by using the CreateNewOutputRows method, and fill in its values later after processing all input rows. However it is not useful to create multiple rows in the CreateNewOutputRows method, because the Script component only lets you use the current row in an input or output. The CreateNewOutputRows method is more important in a source component where there are no input rows to process. You may also want to override the ProcessInput method itself, so that you can do additional preliminary or final processing before or after you loop through the input buffer and call ProcessInputRow for each row. For example, one of the code examples in this topic overrides ProcessInput to count the number of addresses in a specific city as ProcessInputRow loops through rows. The example writes the summary value to the second output after all rows have been processed. The example completes the output in ProcessInput because the output buffers are no longer available when PostExecute is called. Depending on your requirements, you may also want to write script in the PreExecute and PostExecute methods available in the ScriptMain class to perform any preliminary or final processing. Note If you were developing a custom data flow component from scratch, it would be important to override the PrimeOutput method to cache references to the output buffers so that you could add rows of data to the buffers later. In the Script component, this is not necessary because you have an automatically generated class representing each output buffer in the BufferWrapper project item.
Example
This example demonstrates the custom code that is required in the ScriptMain class to create an asynchronous transformation component. Note These examples use the Person.Address table in the AdventureWorks sample database and pass its first and fourth columns, the int AddressID and nvarchar(30) City columns, through the data flow. The same data is used in the source, transformation, and destination samples in this section. Additional prerequisites and assumptions are documented for each example. This example demonstrates an asynchronous transformation component with two outputs. This transformation passes through the AddressID and City columns to one output, while it counts 148
the number of addresses located in a specific city (Redmond, Washington, U.S.A.), and then outputs the resulting value to a second output. If you want to run this sample code, you must configure the package and the component as follows: 1. Add a new Script component to the Data Flow designer surface and configure it as a transformation. 2. Connect the output of a source or of another transformation to the new transformation component in the designer. This output should provide data from the Person.Address table of the AdventureWorks sample database that contains at least the AddressID and City columns. 3. Open the Script Transformation Editor. On the Input Columns page, select the AddressID and City columns. 4. On the Inputs and Outputs page, add and configure the AddressID and City output columns on the first output. Add a second output, and add an output column for the summary value on the second output. Set the SynchronousInputID property of the first output to 0, because this example copies each input row explicitly to the first output. The SynchronousInputID property of the newly-created output is already set to 0. 5. Rename the input, the outputs, and the new output column to give them more descriptive names. The example uses MyAddressInput as the name of the input, MyAddressOutput and MySummaryOutput for the outputs, and MyRedmondCount for the output column on the second output. 6. On the Script page, click Edit Script and enter the script that follows. Then close the script development environment and the Script Transformation Editor. 7. Create and configure a destination component for the first output that expects the AddressID and City columns, such as a SQL Server destination, or the sample destination component demonstrated in Creating a Destination with the Script Component, . Then connect the first output of the transformation, MyAddressOutput, to the destination component. You can create a destination table by running the following Transact-SQL command in the AdventureWorks database:
CREATE TABLE [Person].[Address2]( [AddressID] [int] NOT NULL, [City] [nvarchar](30) NOT NULL )
8. Create and configure another destination component for the second output. Then connect the second output of the transformation, MySummaryOutput, to the destination component. Because the second output writes a single row with a single value, you can easily configure a destination with a Flat File connection manager that connects to a new file that has a single column. In the example, this destination column is named MyRedmondCount. 9. Run the sample. 149
Public Class ScriptMain Inherits UserComponent Private myRedmondAddressCount As Integer Public Overrides Sub CreateNewOutputRows() MySummaryOutputBuffer.AddRow() End Sub Public Overrides Sub MyAddressInput_ProcessInput(ByVal Buffer As MyAddressInputBuffer) While Buffer.NextRow() MyAddressInput_ProcessInputRow(Buffer) End While If Buffer.EndOfRowset Then MyAddressOutputBuffer.SetEndOfRowset() MySummaryOutputBuffer.MyRedmondCount = myRedmondAddressCount MySummaryOutputBuffer.SetEndOfRowset() End If End Sub Public Overrides Sub MyAddressInput_ProcessInputRow(ByVal Row As MyAddressInputBuffer) With MyAddressOutputBuffer .AddRow() .AddressID = Row.AddressID .City = Row.City End With
150
If Row.City.ToUpper = "REDMOND" Then myRedmondAddressCount += 1 End If End Sub End Class public class ScriptMain: UserComponent { private int myRedmondAddressCount; public override void CreateNewOutputRows() { MySummaryOutputBuffer.AddRow(); } public override void MyAddressInput_ProcessInput(MyAddressInputBuffer Buffer) { while (Buffer.NextRow()) { MyAddressInput_ProcessInputRow(Buffer); } if (Buffer.EndOfRowset()) { MyAddressOutputBuffer.SetEndOfRowset(); MySummaryOutputBuffer.MyRedmondCount = myRedmondAddressCount;
151
MySummaryOutputBuffer.SetEndOfRowset(); } } public override void MyAddressInput_ProcessInputRow(MyAddressInputBuffer Row) { { MyAddressOutputBuffer.AddRow(); MyAddressOutputBuffer.AddressID = Row.AddressID; MyAddressOutputBuffer.City = Row.City; } if (Row.City.ToUpper() == "REDMOND") { myRedmondAddressCount += 1; } } }
152
Stay Up to Date with Integration Services For the latest downloads, articles, samples, and videos from Microsoft, as well as selected solutions from the community, visit the Integration Services page on MSDN:
See Also
Understanding Synchronous and Asynchronous Outputs Creating a Synchronous Transformation with the Script Component Developing a Custom Transformation Component with Asynchronous Outputs
After you select the option to create a destination component, you configure the component by using the Script Transformation Editor. For more information, see Configuring the Script Component. To select the script language that the Script destination will use, you set the ScriptLanguage property on the Script page of the Script Transformation Editor dialog box. Note To set the default scripting language for the Script component, use the Scripting language option on the General page of the Options dialog box. For more information, see General Page. A data flow destination component has one input and no outputs. Configuring the input for the component is one of the steps that you must complete in metadata design mode, by using the Script Transformation Editor, before you write your custom script.
Adding Variables
154
If you want to use existing variables in your script, you can add them in the ReadOnlyVariables and ReadWriteVariables property fields on the Script page of the Script Transformation Editor. When you add multiple variables in the property fields, separate the variable names by commas. You can also select multiple variables by clicking the ellipsis () button next to the ReadOnlyVariables and ReadWriteVariables property fields, and then selecting the variables in the Select variables dialog box. For general information about how to use variables with the Script component, see Using Variables in the Script Component. For more information about the Script page of the Script Transformation Editor, see Script Transformation Editor (Script Page).
2. Override the PreExecute method to prepare to save the data. For example, you may want to create and configure a SqlCommand and its parameters in this method. 3. Use the overridden ProcessInputRow method to copy each input row to the external data source. For example, for a SQL Server destination, you can copy the column values into the parameters of a SqlCommand and execute the command one time for each row. For a flat file destination, you can write the values for each column to a StreamWriter, separating the values by the column delimiter. 4. Override the PostExecute method to disconnect from the external data source, if required, and to perform any other required cleanup.
Examples
The examples that follow demonstrate code that is required in the ScriptMain class to create a destination component. Note These examples use the Person.Address table in the AdventureWorks sample database and pass its first and fourth columns, the int AddressID and nvarchar(30) City columns, through the data flow. The same data is used in the source, transformation, and destination samples in this section. Additional prerequisites and assumptions are documented for each example.
3. Add a new Script component to the Data Flow designer surface and configure it as a destination. 4. Connect the output of an upstream source or transformation to the destination component in SSIS Designer. (You can connect a source directly to a destination without any transformations.) This output should provide data from the Person.Address table of the AdventureWorks sample database that contains at least the AddressID and City columns.
156
5. Open the Script Transformation Editor. On the Input Columns page, select the AddressID and City input columns. 6. On the Inputs and Outputs page, rename the input with a more descriptive name such as MyAddressInput. 7. On the Connection Managers page, add or create the ADO.NET connection manager with a name such as MyADONETConnectionManager. 8. On the Script page, click Edit Script and enter the script that follows. Then close the script development environment. 9. Close the Script Transformation Editor and run the sample.
Imports System.Data.SqlClient ... Public Class ScriptMain Inherits UserComponent Dim connMgr As IDTSConnectionManager100 Dim sqlConn As SqlConnection Dim sqlCmd As SqlCommand Dim sqlParam As SqlParameter Public Overrides Sub AcquireConnections(ByVal Transaction As Object) connMgr = Me.Connections.MyADONETConnectionManager sqlConn = CType(connMgr.AcquireConnection(Nothing), SqlConnection) End Sub Public Overrides Sub PreExecute() sqlCmd = New SqlCommand("INSERT INTO Person.Address2(AddressID, City) " & _ "VALUES(@addressid, @city)", sqlConn) sqlParam = New SqlParameter("@addressid", SqlDbType.Int) sqlCmd.Parameters.Add(sqlParam) sqlParam = New SqlParameter("@city", SqlDbType.NVarChar, 30) sqlCmd.Parameters.Add(sqlParam)
157
End Sub Public Overrides Sub MyAddressInput_ProcessInputRow(ByVal Row As MyAddressInputBuffer) With sqlCmd .Parameters("@addressid").Value = Row.AddressID .Parameters("@city").Value = Row.City .ExecuteNonQuery() End With End Sub Public Overrides Sub ReleaseConnections() connMgr.ReleaseConnection(sqlConn) End Sub End Class using System.Data.SqlClient; public class ScriptMain: UserComponent { IDTSConnectionManager100 connMgr; SqlConnection sqlConn; SqlCommand sqlCmd; SqlParameter sqlParam; public override void AcquireConnections(object Transaction) { connMgr = this.Connections.MyADONETConnectionManager; sqlConn = (SqlConnection)connMgr.AcquireConnection(null);
158
} public override void PreExecute() { sqlCmd = new SqlCommand("INSERT INTO Person.Address2(AddressID, City) " + "VALUES(@addressid, @city)", sqlConn); sqlParam = new SqlParameter("@addressid", SqlDbType.Int); sqlCmd.Parameters.Add(sqlParam); sqlParam = new SqlParameter("@city", SqlDbType.NVarChar, 30); sqlCmd.Parameters.Add(sqlParam); } public override void MyAddressInput_ProcessInputRow(MyAddressInputBuffer Row) { { sqlCmd.Parameters["@addressid"].Value = Row.AddressID; sqlCmd.Parameters["@city"].Value = Row.City; sqlCmd.ExecuteNonQuery(); } } public override void ReleaseConnections() { connMgr.ReleaseConnection(sqlConn); } }
159
160
copiedAddressFile = CType(connMgr.AcquireConnection(Nothing), String) End Sub Public Overrides Sub PreExecute() textWriter = New StreamWriter(copiedAddressFile, False) End Sub Public Overrides Sub MyAddressInput_ProcessInputRow(ByVal Row As MyAddressInputBuffer) With textWriter If Not Row.AddressID_IsNull Then .Write(Row.AddressID) End If .Write(columnDelimiter) If Not Row.City_IsNull Then .Write(Row.City) End If .WriteLine() End With End Sub Public Overrides Sub PostExecute() textWriter.Close() End Sub End Class using System.IO;
161
public class ScriptMain: UserComponent { string copiedAddressFile; private StreamWriter textWriter; private string columnDelimiter = ","; public override void AcquireConnections(object Transaction) { IDTSConnectionManager100 connMgr = this.Connections.MyFlatFileDestConnectionManager; copiedAddressFile = (string) connMgr.AcquireConnection(null); } public override void PreExecute() { textWriter = new StreamWriter(copiedAddressFile, false); } public override void MyAddressInput_ProcessInputRow(MyAddressInputBuffer Row) { { if (!Row.AddressID_IsNull) { textWriter.Write(Row.AddressID); } textWriter.Write(columnDelimiter);
162
Stay Up to Date with Integration Services For the latest downloads, articles, samples, and videos from Microsoft, as well as selected solutions from the community, visit the Integration Services page on MSDN:
See Also
Creating a Source with the Script Component 163
In This Section
Simulating an Error Output for the Script Component
The Script component does not support a standard error output, but you can simulate a standard error output with very little additional configuration and coding.
164
Stay Up to Date with Integration Services For the latest downloads, articles, samples, and videos from Microsoft, as well as selected solutions from the community, visit the Integration Services page on MSDN:
Example
The example shown here uses a Script component configured as a transformation that has two synchronous outputs. The purpose of the Script component is to filter error rows from address data in the AdventureWorks sample database. This fictitious example assumes that we are preparing a promotion for North American customers and need to filter out addresses that are not located in North America. To configure the example 1. Before creating the new Script component, create a connection manager and configure 165
a data flow source that selects address data from the AdventureWorks sample database. For this example, which only looks at the CountryRegionName column, you can simply use the Person.vStateCountryProvinceRegion view, or you can select data by joining the Person.Address, Person.StateProvince, and Person.CountryRegion tables. 2. Add a new Script component to the Data Flow designer surface and configure it as a transformation. Open the Script Transformation Editor. 3. On the Script page, set the ScriptLanguage property to the script language that you want to use to code the script. 4. Click Edit Script to open Microsoft Visual Studio Tools for Applications (VSTA). 5. In the Input0_ProcessInputRow method, type or paste the sample code shown below. 6. Close VSTA. 7. On the Input Columns page, select the columns that you want to process in the Script transformation. This example uses only the CountryRegionName column. Available input columns that you leave unselected will simply be passed through unchanged in the data flow. 8. On the Inputs and Outputs page, add a new, second output, and set its SynchronousInputID value to the ID of the input, which is also the value of the SynchronousInputID property of the default output. Set the ExclusionGroup property of both outputs to the same non-zero value (for example, 1) to indicate that each row will be directed to only one of the two outputs. Give the new error output a distinctive name, such as "MyErrorOutput." 9. Add additional output columns to the new error output to capture the desired error information, which may include the error code, the ID of the column in which the error occurred, and possibly the error description. This example creates the new columns, ErrorColumn and ErrorMessage. If you are catching predefined Integration Services errors in your own implementation, make sure to add an ErrorCode column for the error number. 10. Note the ID value of the input column or columns that the Script component will check for error conditions. This example uses this column identifier to populate the ErrorColumn value. 11. Close the Script Transformation Editor. 12. Attach the outputs of the Script component to suitable destinations. Flat file destinations are the easiest to configure for ad hoc testing. 13. Run the package.
Public Overrides Sub Input0_ProcessInputRow(ByVal Row As Input0Buffer) If Row.CountryRegionName <> "Canada" _ And Row.CountryRegionName <> "United States" Then
166
Row.ErrorColumn = 68 ' ID of CountryRegionName column Row.ErrorMessage = "Address is not in North America." Row.DirectRowToMyErrorOutput() Else Row.DirectRowToOutput0() End If End Sub public override void Input0_ProcessInputRow(Input0Buffer Row) { if (Row.CountryRegionName!="Canada"&&Row.CountryRegionName!="United States") { Row.ErrorColumn = 68; // ID of CountryRegionName column Row.ErrorMessage = "Address is not in North America."; Row.DirectRowToMyErrorOutput(); } else { Row.DirectRowToOutput0(); } }
167
Stay Up to Date with Integration Services For the latest downloads, articles, samples, and videos from Microsoft, as well as selected solutions from the community, visit the Integration Services page on MSDN:
See Also
Handling Errors in Data Using Error Outputs Creating a Synchronous Transformation with the Script Component
Note If you want to create a component that you can more easily reuse across multiple Data Flow tasks and multiple packages, consider using the code in this Script component sample as the starting point for a custom data flow component. For more information, see Extending the Data Flow with Custom Components. 168
Example
The example shown here uses a Script component configured as a transformation to add an error description column to existing error output data in the data flow. For more information about how to configure the Script component for use as a transformation in the data flow, see Creating a Synchronous Transformation with the Script Component and Creating an Asynchronous Transformation with the Script Component. To configure this Script Component example 1. Before creating the new Script component, configure an upstream component in the data flow to redirect rows to its error output when an error or truncation occurs. For testing purposes, you may want to configure a component in a manner that ensures that errors will occurfor example, by configuring a Lookup transformation between two tables where the lookup will fail. 2. Add a new Script component to the Data Flow designer surface and configure it as a transformation. 3. Connect the error output from the upstream component to the new Script component. 4. Open the Script Transformation Editor, and on the Script page, for the ScriptLanguage property, select the script language. 5. Click Edit Script to open the Microsoft Visual Studio Tools for Applications (VSTA) IDE and add the sample code shown below. 6. Close VSTA. 7. In the Script Transformation Editor, on the Input Columns page, select the ErrorCode column. 8. On the Inputs and Outputs page, add a new output column of type String named ErrorDescription. Increase the default length of the new column to 255 to support long messages. 9. Close the Script Transformation Editor. 10. Attach the output of the Script component to a suitable destination. A Flat File destination is the easiest to configure for ad hoc testing. 11. Run the package.
Public Class ScriptMain Inherits UserComponent Public Overrides Sub Input0_ProcessInputRow(ByVal Row As Input0Buffer) Row.ErrorDescription = _ Me.ComponentMetaData.GetErrorDescription(Row.ErrorCode)
169
End Sub End Class public class ScriptMain: UserComponent { public override void Input0_ProcessInputRow(Input0Buffer Row) { Row.ErrorDescription = this.ComponentMetaData.GetErrorDescription(Row.ErrorCode); } }
Stay Up to Date with Integration Services For the latest downloads, articles, samples, and videos from Microsoft, as well as selected solutions from the community, visit the Integration Services page on MSDN:
See Also
Handling Errors in Data Using Error Outputs Creating a Synchronous Transformation with the Script Component
170
Example
The following example demonstrates how to create a destination component that uses an existing ODBC connection manager to save data from the data flow into a Microsoft SQL Server table. This example is a modified version of the custom ADO.NET destination that was demonstrated in the topic, Creating a Destination with the Script Component. However, in this example, the custom ADO.NET destination has been modified to work with an ODBC connection manager and save data to an ODBC destination. These modifications also include the following changes: You cannot call the AcquireConnection method of the ODBC connection manager from managed code, because it returns a native object. Therefore, this sample uses the connection string of the connection manager to connect to the data source directly by using the managed ODBC .NET Framework Data Provider. The OdbcCommand expects positional parameters. The positions of the parameters are indicated by the question marks (?) in the text of the command. (In contrast, a SqlCommand expects named parameters.)
This example uses the Person.Address table in the AdventureWorks sample database. The example passes the first and fourth columns, the int AddressID and nvarchar(30) City columns, of this table through the data flow. This same data is used in the source, transformation, and destination samples in the topic, Developing Specific Types of Script Components. To configure this Script Component example 1. Create an ODBC connection manager that connects to the AdventureWorks database. 2. Create a destination table by running the following Transact-SQL command in the AdventureWorks database:
CREATE TABLE [Person].[Address2]( [AddressID] [int] NOT NULL, [City] [nvarchar](30) NOT NULL )
171
3. Add a new Script component to the Data Flow designer surface and configure it as a destination. 4. Connect the output of an upstream source or transformation to the destination component in SSIS Designer. (You can connect a source directly to a destination without any transformations.) To ensure that this sample works, the output of the upstream component must include at least the AddressID and City columns from the Person.Address table of the AdventureWorks sample database. 5. Open the Script Transformation Editor. On the Input Columns page, select the AddressID and City columns. 6. On the Inputs and Outputs page, rename the input with a more descriptive name such as MyAddressInput. 7. On the Connection Managers page, add or create the ODBC connection manager with a descriptive name such as MyODBCConnectionManager. 8. On the Script page, click Edit Script, and then enter the script shown below in the ScriptMain class. 9. Close the script development environment, close the Script Transformation Editor, and then run the sample.
Imports System.Data.Odbc ... Public Class ScriptMain Inherits UserComponent Dim odbcConn As OdbcConnection Dim odbcCmd As OdbcCommand Dim odbcParam As OdbcParameter Public Overrides Sub AcquireConnections(ByVal Transaction As Object) Dim connectionString As String connectionString = Me.Connections.MyODBCConnectionManager.ConnectionString odbcConn = New OdbcConnection(connectionString) odbcConn.Open() End Sub
172
Public Overrides Sub PreExecute() odbcCmd = New OdbcCommand("INSERT INTO Person.Address2(AddressID, City) " & _ "VALUES(?, ?)", odbcConn) odbcParam = New OdbcParameter("@addressid", OdbcType.Int) odbcCmd.Parameters.Add(odbcParam) odbcParam = New OdbcParameter("@city", OdbcType.NVarChar, 30) odbcCmd.Parameters.Add(odbcParam) End Sub Public Overrides Sub MyAddressInput_ProcessInputRow(ByVal Row As MyAddressInputBuffer) With odbcCmd .Parameters("@addressid").Value = Row.AddressID .Parameters("@city").Value = Row.City .ExecuteNonQuery() End With End Sub Public Overrides Sub ReleaseConnections() odbcConn.Close() End Sub End Class using System.Data.Odbc;
173
... public class ScriptMain : UserComponent { OdbcConnection odbcConn; OdbcCommand odbcCmd; OdbcParameter odbcParam; public override void AcquireConnections(object Transaction) { string connectionString; connectionString = this.Connections.MyODBCConnectionManager.ConnectionString; odbcConn = new OdbcConnection(connectionString); odbcConn.Open(); } public override void PreExecute() { odbcCmd = new OdbcCommand("INSERT INTO Person.Address2(AddressID, City) " + "VALUES(?, ?)", odbcConn); odbcParam = new OdbcParameter("@addressid", OdbcType.Int); odbcCmd.Parameters.Add(odbcParam); odbcParam = new OdbcParameter("@city", OdbcType.NVarChar, 30); odbcCmd.Parameters.Add(odbcParam); }
174
public override void MyAddressInput_ProcessInputRow(MyAddressInputBuffer Row) { { odbcCmd.Parameters["@addressid"].Value = Row.AddressID; odbcCmd.Parameters["@city"].Value = Row.City; odbcCmd.ExecuteNonQuery(); } } public override void ReleaseConnections() { odbcConn.Close(); } }
175
Stay Up to Date with Integration Services For the latest downloads, articles, samples, and videos from Microsoft, as well as selected solutions from the community, visit the Integration Services page on MSDN:
See Also
Creating a Destination with the Script Component
1. Create and save a text file named rowdelimiteddata.txt that contains the following source data:
FirstName: Nancy LastName: Davolio Title: Sales Representative City: Seattle StateProvince: WA FirstName: Andrew LastName: Fuller Title: Vice President, Sales City: Tacoma StateProvince: WA FirstName: Steven LastName: Buchanan Title: Sales Manager City: London StateProvince:
2. Open Management Studio and connect to an instance of SQL Server. 3. Select a destination database, and open a new query window. In the query window, execute the following script to create the destination table:
create table RowDelimitedData ( FirstName varchar(32), LastName varchar(32), Title varchar(32), City varchar(32), StateProvince varchar(32) )
4. Open SQL Server Data Tools and create a new Integration Services package named ParseRowDelim.dtsx. 177
5. Add a Flat File connection manager to the package, name it RowDelimitedData, and configure it to connect to the rowdelimiteddata.txt file that you created in a previous step. 6. Add an OLE DB connection manager to the package and configure it to connect to the instance of SQL Server and the database in which you created the destination table. 7. Add a Data Flow task to the package and click the Data Flow tab of SSIS Designer. 8. Add a Flat File Source to the data flow and configure it to use the RowDelimitedData connection manager. On the Columns page of the Flat File Source Editor, select the single available external column. 9. Add a Script Component to the data flow and configure it as a transformation. Connect the output of the Flat File Source to the Script Component. 10. Double-click the Script component to display the Script Transformation Editor. 11. On the Input Columns page of the Script Transformation Editor, select the single available input column. 12. On the Inputs and Outputs page of the Script Transformation Editor, select Output 0 and set its SynchronousInputID to None. Create 5 output columns, all of type string [DT_STR] with a length of 32: FirstName LastName Title City StateProvince
13. On the Script page of the Script Transformation Editor, click Edit Script and enter the code shown in the ScriptMain class of the example. Close the script development environment and the Script Transformation Editor. 14. Add a SQL Server Destination to the data flow. Configure it to use the OLE DB connection manager and the RowDelimitedData table. Connect the output of the Script Component to this destination. 15. Run the package. After the package has finished, examine the records in the SQL Server destination table.
Public Overrides Sub Input0_ProcessInputRow(ByVal Row As Input0Buffer) Dim columnName As String Dim columnValue As String ' Check for an empty row. If Row.Column0.Trim.Length > 0 Then
178
columnName = Row.Column0.Substring(0, Row.Column0.IndexOf(":")) ' Check for an empty value after the colon. If Row.Column0.Substring(Row.Column0.IndexOf(":")).TrimEnd.Length > 1 Then ' Extract the column value from after the colon and space. columnValue = Row.Column0.Substring(Row.Column0.IndexOf(":") + 2) Select Case columnName Case "FirstName" ' The FirstName value indicates a new record. Me.Output0Buffer.AddRow() Me.Output0Buffer.FirstName = columnValue Case "LastName" Me.Output0Buffer.LastName = columnValue Case "Title" Me.Output0Buffer.Title = columnValue Case "City" Me.Output0Buffer.City = columnValue Case "StateProvince" Me.Output0Buffer.StateProvince = columnValue End Select End If End If End Sub public override void Input0_ProcessInputRow(Input0Buffer Row) { string columnName; string columnValue; // Check for an empty row. if (Row.Column0.Trim().Length > 0) {
179
columnName = Row.Column0.Substring(0, Row.Column0.IndexOf(":")); // Check for an empty value after the colon. if (Row.Column0.Substring(Row.Column0.IndexOf(":")).TrimEnd().Length > 1) // Extract the column value from after the colon and space. { columnValue = Row.Column0.Substring(Row.Column0.IndexOf(":") + 2); switch (columnName) { case "FirstName": // The FirstName value indicates a new record. this.Output0Buffer.AddRow(); this.Output0Buffer.FirstName = columnValue; break; case "LastName": this.Output0Buffer.LastName = columnValue; break; case "Title": this.Output0Buffer.Title = columnValue; break; case "City": this.Output0Buffer.City = columnValue; break; case "StateProvince": this.Output0Buffer.StateProvince = columnValue; break; } } } }
This example shows how to take a text file, in which a separator row precedes a parent record row that is followed by an indefinite number of child record rows, and parse it into properly normalized parent and child destination tables by using the Script component. This simple example could easily be adapted for source files that use more than one row or column for each parent and child record, as long as there is some way to identify the beginning and end of each record. Caution This sample is intended for demonstration purposes only. If you run the sample more than once, it inserts duplicate key values into the destination table. For more information about how to configure the Script component for use as a transformation in the data flow, see Creating a Synchronous Transformation with the Script Component and Creating an Asynchronous Transformation with the Script Component. To configure this Script Component example 1. Create and save a text file named parentchilddata.txt that contains the following source data:
********** PARENT 1 DATA child 1 data child 2 data child 3 data child 4 data ********** PARENT 2 DATA child 5 data child 6 data child 7 data child 8 data **********
2. Open SQL Server Management Studio and connect to an instance of SQL Server. 3. Select a destination database, and open a new query window. In the query window, execute the following script to create the destination tables:
CREATE TABLE [dbo].[Parents]( [ParentID] [int] NOT NULL, [ParentRecord] [varchar](32) NOT NULL,
181
CONSTRAINT [PK_Parents] PRIMARY KEY CLUSTERED ([ParentID] ASC) ) GO CREATE TABLE [dbo].[Children]( [ChildID] [int] NOT NULL, [ParentID] [int] NOT NULL, [ChildRecord] [varchar](32) NOT NULL, CONSTRAINT [PK_Children] PRIMARY KEY CLUSTERED ([ChildID] ASC) ) GO ALTER TABLE [dbo].[Children] ADD CONSTRAINT [FK_Children_Parents] FOREIGN KEY([ParentID]) REFERENCES [dbo].[Parents] ([ParentID])
4. Open SQL Server Data Tools (SSDT) and create a new Integration Services package named SplitParentChild.dtsx. 5. Add a Flat File connection manager to the package, name it ParentChildData, and configure it to connect to the parentchilddata.txt file that you created in a previous step. 6. Add an OLE DB connection manager to the package and configure it to connect to the instance of SQL Server and the database in which you created the destination tables. 7. Add a Data Flow task to the package and click the Data Flow tab of SSIS Designer. 8. Add a Flat File Source to the data flow and configure it to use the ParentChildData connection manager. On the Columns page of the Flat File Source Editor, select the single available external column. 9. Add a Script Component to the data flow and configure it as a transformation. Connect the output of the Flat File Source to the Script Component. 10. Double-click the Script component to display the Script Transformation Editor. 11. On the Input Columns page of the Script Transformation Editor, select the single available input column. 12. On the Inputs and Outputs page of the Script Transformation Editor, select Output 0, rename it to ParentRecords, and set its SynchronousInputID to None. Create 2 output columns: ParentID (the primary key), of type four-byte signed integer [DT_I4] 182
13. Create a second output and name it ChildRecords. The SynchronousInputID of the new output is already set to None. Create 3 output columns: ChildID (the primary key), of type four-byte signed integer [DT_I4] ParentID (the foreign key), also of type four-byte signed integer [DT_I4] ChildRecord, of type string [DT_STR] with a length of 50
14. On the Script page of the Script Transformation Editor, click Edit Script. In the ScriptMain class, enter the code shown in the example. Close the script development environment and the Script Transformation Editor. 15. Add a SQL Server Destination to the data flow. Connect the ParentRecords output of the Script Component to this destination.Configure it to use the OLE DB connection manager and the Parents table. 16. Add another SQL Server Destination to the data flow. Connect the ChildRecords output of the Script Component to this destination. Configure it to use the OLE DB connection manager and the Children table. 17. Run the package. After the package has finished, examine the parent and child records in the two SQL Server destination tables.
Public Overrides Sub Input0_ProcessInputRow(ByVal Row As Input0Buffer) Static nextRowIsParent As Boolean = False Static parentCounter As Integer = 0 Static childCounter As Integer = 0 ' If current row starts with separator characters, ' then following row contains new parent record. nextRowIsParent = True Else If nextRowIsParent Then ' Current row contains parent record. parentCounter += 1 Me.ParentRecordsBuffer.AddRow() Me.ParentRecordsBuffer.ParentID = parentCounter Me.ParentRecordsBuffer.ParentRecord = Row.Column0 nextRowIsParent = False Else If Row.Column0.StartsWith("***") Then
183
' Current row contains child record. childCounter += 1 Me.ChildRecordsBuffer.AddRow() Me.ChildRecordsBuffer.ChildID = childCounter Me.ChildRecordsBuffer.ParentID = parentCounter Me.ChildRecordsBuffer.ChildRecord = Row.Column0 End If End If End Sub public override void Input0_ProcessInputRow(Input0Buffer Row) { int static_Input0_ProcessInputRow_childCounter = 0; int static_Input0_ProcessInputRow_parentCounter = 0; bool static_Input0_ProcessInputRow_nextRowIsParent = false; // If current row starts with separator characters, // then following row contains new parent record. if (Row.Column0.StartsWith("***")) { static_Input0_ProcessInputRow_nextRowIsParent = true; } else { if (static_Input0_ProcessInputRow_nextRowIsParent) { // Current row contains parent record. static_Input0_ProcessInputRow_parentCounter += 1; this.ParentRecordsBuffer.AddRow(); this.ParentRecordsBuffer.ParentID = static_Input0_ProcessInputRow_parentCounter; this.ParentRecordsBuffer.ParentRecord = Row.Column0; static_Input0_ProcessInputRow_nextRowIsParent = false;
184
} else { // Current row contains child record. static_Input0_ProcessInputRow_childCounter += 1; this.ChildRecordsBuffer.AddRow(); this.ChildRecordsBuffer.ChildID = static_Input0_ProcessInputRow_childCounter; this.ChildRecordsBuffer.ParentID = static_Input0_ProcessInputRow_parentCounter; this.ChildRecordsBuffer.ChildRecord = Row.Column0; } } }
Stay Up to Date with Integration Services For the latest downloads, articles, samples, and videos from Microsoft, as well as selected solutions from the community, visit the Integration Services page on MSDN:
See Also
Creating a Synchronous Transformation with the Script Component Creating an Asynchronous Transformation with the Script Component
185