T4 texttransform passing parameters on command line possible? - visual-studio-2013

I want to run the t4 TextTransForm.exe utility on my build server on the command line. I'm aware that the DTE object etc are not available on the command line.
But executing a simple transform on a template which expects a parameter also suggests the parameter directive is not working on the command line.
C:\util>"C:\Program Files (x86)\Common Files\Microsoft Shared\TextTemplating\12.0\TextTransform.exe" test.tt -a !!MyParameter!test
error : Errors were generated when initializing the transformation object. The transformation will not be run. The following Exception was thrown:
System.NullReferenceException: Object reference not set to an instance of an object.
at Microsoft.VisualStudio.TextTemplating9edb37733d3e4e5f96a377656fe05b5c.GeneratedTextTransformation.Initialize()
at CallSite.Target(Closure , CallSite , Object )
at System.Dynamic.UpdateDelegates.UpdateAndExecuteVoid1[T0](CallSite site, T0 arg0)
at Microsoft.VisualStudio.TextTemplating.TransformationRunner.PerformTransformation()
This is my test.tt template:
<## template language="C#" hostspecific="true" #>
<## parameter name="MyParameter" type="System.String" #>
Parameter in expression block: <#= MyParameter #>
Parameter in statement block: <# Write(MyParameter); #>
Looking at discussions like
Get argument value from TextTransform.exe into the template gave me the impression it would also work on the command line without a specific host or Visual studio installed.
So am I doing something wrong, or will this just not work on the command line?

The parameter directive was the line which caused the error. Just remove it and your can read the value of the parameter with:
this.Host.ResolveParameterValue("","","MyParameter");
So the working .tt looked like this:
<## template language="C#" hostspecific="true" #>
<## import namespace="Microsoft.VisualStudio.TextTemplating" #>
Parameter value is
<#
var MyParameter= this.Host.ResolveParameterValue("","","MyParameter");
Console.WriteLine("MyParameter is: " + MyParameter);
#>

hkstr's answer is almost correct, but the "Host.ResolveParameterValue()" call fails when used inside Visual Studio. The answer is to wrap the call in try...catch or to check for DTE. In my case the DTE had the information I wanted:
<## template debug="false" hostspecific="true" language="C#" #>
<## assembly name="System.Core" #>
<## assembly name="EnvDTE" #>
<## import namespace="EnvDTE" #>
<#
string activeConfiguration = null;
// The IServiceProvider is available in VS but isn't available on the command line.
IServiceProvider serviceProvider = Host as IServiceProvider;
if (serviceProvider != null)
{
DTE dte = (DTE)serviceProvider.GetService(typeof(DTE));
activeConfiguration = dte.Solution.SolutionBuild.ActiveConfiguration.Name;
}
else
{
activeConfiguration = this.Host.ResolveParameterValue("", "", "ActiveConfiguration");
}
Console.WriteLine("ActiveConfiguration is: " + activeConfiguration);
#>

Related

Syntax for square brackets in a T4 Template statement block

I would like to use the System.Configuration assembly within a T4 template to get the connection string listed in the App.config of the project. However, the compiler does not seem to accept the [ ] in the statement block. How is this done?
<## assembly name="System.Configuration" #>
<## import namespace="System.Configuration"#>
<#
var connectionString = ConfigurationManager.ConnectionStrings["localconnection"].ConnectionString;
#>
TIA
If you're running T4 at design time (CustomTool: TextTemplatingFilePreprocessor), the template code gets executed as part of VisualStudio process. VisualStudio is loading devenv.exe.config and not your project config (you can check via AppDomain.CurrentDomain.SetupInformation.ConfigurationFile).
That's why you get null ref exception - 'localconnection' connection string is not in devenv.exe.config.
You can load your project config file using ConfigurationManager.OpenMappedExeConfiguration:
<## template debug="false" hostspecific="true" language="C#" #>
<## assembly name="System.Configuration" #>
<## import namespace="System.Configuration"#>
<## import namespace="System.IO" #>
<#
string configPath = Path.Combine(Host.ResolveAssemblyReference("$(ProjectDir)"), "App.config");
var configFileMap = new ExeConfigurationFileMap{ExeConfigFilename = configPath};
var config = ConfigurationManager.OpenMappedExeConfiguration(configFileMap, ConfigurationUserLevel.None);
string connectionString = config.ConnectionStrings.ConnectionStrings["localconnection"].ConnectionString;
WriteLine(connectionString);
#>
Note, it must be hostspecific="true" in order to use Host to resolve your project folder.

T4 dynamic include directive

I'm using T4 to generate some scripts as runtime templates. A part of this is to read some text files that include some T4 directive the for each file generate an output using a master template.
Each file may contain text such as:
Create table foo (
rid bigint identity(<#= db.DefaultSeed #>, <#= db.DefaultIncrement #>),
...other table specific stuff
I have a template that includes the object data for the db referenced in the other files and some source file reference.
The main template to run could be like:
<## template language="C#" #>
<## assembly name="System.Core" #>
<## import namespace="System.Linq" #>
<## import namespace="System.Text" #>
<## import namespace="System.Collections.Generic" #>
<## include file="Src" #>
Where src would refer to a variable passed to the template.
Then I would like to have a string list of files that could be iterated over to build individual scripts so that the following could be done in say a console program:
public void BuildTables(){
List<string> files = getFiles();//just get a list of files in a folder to process
foreach(var f in files){
var mainTemplate = new MainTemplate(dbSettings);
mainTemplate.Src = f;
String text = mainTemplate.TransformText();
System.IO.File.WriteAllText(outPath + f, text);
}
}
Howerver, it looks like the include directive is unable to accept variables like:
<## include file="<#= Src #>" #>
Is there a way to inject the text into the template file?

SSIS For Loop resetting the variable value

In SSIS (MS Visual Studio 2010) I have created a SQL Task with Single Row output (int32) that is passed to User::Variable. Then I have a "For Loop" that I want to run as many times as the integer from the SQL Task query equals to. I have setup the For Loop with the following:
InitExpression: #Start = 1
EvalExpression: #Variable >= #Start
AssignExpression: #Start = #Start+1
In my test example, the #Variable is set to 4 by SQL Task query, and the loop runs fine for the first loop, but then immediately finishes reporting package successfully executed (after just 1 loop when it should have looped 4 times).
It is like the #Variable resets to default value (0) after the first loop is done instead of keeping the original value (4).
How can I fix this?
What's more likely: using the name Variable, which SSIS defaults to for a new variable, is incompatible with anything in an SSIS package or that you've done something that you haven't shown?
I have created a package with two Variables, Variable and Start. I have populated Variable from a SQL Query with the value of 4 and then used a For Loop as described and I did not experience early termination.
My package, variables with design time values set to zero.
My For Loop Container
Last iteration of the loop
Reproducible results
Biml, the Business Intelligence Markup Language, describes the platform for business intelligence. Here, we're going to use it to describe the ETL. BIDS Helper, is a free add on for Visual Studio/BIDS/SSDT that addresses a host of shortcomings with it.
You will need to edit line 3 to be a valid OLE DB Connection Manager for your machine (point to a SQL Server installation and possibly update the Provider)
<Biml xmlns="http://schemas.varigence.com/biml.xsd">
<Connections>
<OleDbConnection Name="CM_OLE" ConnectionString="Data Source=localhost\dev2012;Initial Catalog=tempdb;Provider=SQLNCLI11;Integrated Security=SSPI;" />
</Connections>
<Packages>
<Package Name="so_25878932" ConstraintMode="Linear">
<Variables>
<Variable Name="Variable" DataType="Int32">0</Variable>
<Variable Name="Start" DataType="Int32">0</Variable>
</Variables>
<Tasks>
<ExecuteSQL ConnectionName="CM_OLE" Name="SQL Variable" ResultSet="SingleRow">
<DirectInput>SELECT 4 AS Variable;</DirectInput>
<Results>
<Result Name="0" VariableName="User.Variable"/>
</Results>
</ExecuteSQL>
<ForLoop ConstraintMode="Linear" Name="FLC Go">
<InitializerExpression>#Start = 1</InitializerExpression>
<LoopTestExpression>#Variable >= #Start</LoopTestExpression>
<CountingExpression>#Start = #Start + 1</CountingExpression>
<Tasks>
<!--
Pilfered from http://bimlscript.com/Snippet/Details/74
-->
<Script
ProjectCoreName="ST_232fecafb70a4e8a904cc21f8870eed0"
Name="SCR Emit Variable and Start">
<ReadOnlyVariables>
<ReadOnlyVariable VariableName="User.Variable" />
<ReadOnlyVariable VariableName="User.Start" />
</ReadOnlyVariables>
<ScriptTaskProject>
<ScriptTaskProject ProjectCoreName="ST_c41ad4bf47544c49ad46f4440163feae" Name="TaskScriptProject1">
<AssemblyReferences>
<AssemblyReference AssemblyPath="Microsoft.SqlServer.ManagedDTS.dll" />
<AssemblyReference AssemblyPath="Microsoft.SqlServer.ScriptTask.dll" />
<AssemblyReference AssemblyPath="System.dll" />
<AssemblyReference AssemblyPath="System.AddIn.dll" />
<AssemblyReference AssemblyPath="System.Data.dll" />
<AssemblyReference AssemblyPath="System.Windows.Forms.dll" />
<AssemblyReference AssemblyPath="System.Xml.dll" />
</AssemblyReferences>
<Files>
<File Path="AssemblyInfo.cs">
using System.Reflection;
using System.Runtime.CompilerServices;
//
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
//
[assembly: AssemblyTitle("ST_c41ad4bf47544c49ad46f4440163feae.csproj")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Varigence")]
[assembly: AssemblyProduct("ST_c41ad4bf47544c49ad46f4440163feae.csproj")]
[assembly: AssemblyCopyright("Copyright # Varigence 2013")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: AssemblyVersion("1.0.*")]
</File>
<File Path="ScriptMain.cs">
using System;
using System.Data;
using Microsoft.SqlServer.Dts.Runtime;
using System.Windows.Forms;
// if SSIS2012, use the following line:
[Microsoft.SqlServer.Dts.Tasks.ScriptTask.SSISScriptTaskEntryPointAttribute]
// if earlier version, use the next line instead of the above line:
// [System.AddIn.AddIn("ScriptMain", Version = "1.0", Publisher = "", Description = "")]
public partial class ScriptMain : Microsoft.SqlServer.Dts.Tasks.ScriptTask.VSTARTScriptObjectModelBase
{
enum ScriptResults
{
Success = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Success,
Failure = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Failure
};
public void Main()
{
int i = 0;
MessageBox.Show(string.Format("{0}:{1}", Dts.Variables[i].Name, Dts.Variables[i].Value));
i++;
MessageBox.Show(string.Format("{0}:{1}", Dts.Variables[i].Name, Dts.Variables[i].Value));
Dts.TaskResult = (int)ScriptResults.Success;
}
}
</File>
</Files>
</ScriptTaskProject>
</ScriptTaskProject>
</Script>
<ExecuteProcess Executable="cmd.exe" Name="EP Do nothing" Arguments="/C">
</ExecuteProcess>
</Tasks>
</ForLoop>
</Tasks>
</Package>
</Packages>
</Biml>
Root cause guess
Variables can be created at different scopes within a package. I would presume that since a variable is created with the default name of Variable two, or more, such variables exist with the same name but at different application scopes. Thus, if I had defined a second SSIS variable named Variable on my ForLoop Container, it would start with a value of 0, unless defined otherwise. My package execution would begin, assign the value of 4 to my package level Variable. Control switches to my ForLoop Container. The Variable defined there wins from a scoping perspective so my value there is 0, not 4 because nothing has initialized it. In that case, the ForLoop would never fire as the terminal condition has already been met. For your case, I further presume that someone initialized that variable to 1.
Notice the Scope here, this is what I believe to be your root cause.
Solution:
DO NOT name SSIS variables #Variable. Doing so will unintentionally cause the variable value to pick up exit codes of other processes in the package as they run.
In my case the #Variable value would automatically change to 0 after a successfully run Execute Process Task with exit code 0; or change to -1073741510 when that process run failed.

MVC3/T4 Custom Scaffolder Gives File Exists Error Even When File Doesn't Exist

I have written a custom scaffolder for MVC3 using T4 templates to scaffold a delete stored procedure for a database and table passed to the scaffolder as parameters. When I run the scaffolder the output file is created in the correct place, but I get the following error:
Invoke-ScaffoldTemplate : Unable to add 'UpdateCustomerCoupon.sql'. A
file with that name already exists. At line:1 char:23
+ param($c, $a) return . <<<< $c #a
+ CategoryInfo : NotSpecified: (:) [Invoke-ScaffoldTemplate], COMException
+ FullyQualifiedErrorId : T4Scaffolding.Cmdlets.InvokeScaffoldTemplateCmdlet
It doesn't matter if the file exists at the output location or not. I get this error every time.
Here is the PowerShell script for the scaffolder:
[T4Scaffolding.Scaffolder(Description = "Enter a description of DeleteSQL here")][CmdletBinding()]
param(
[parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)][string]$DatabaseName,
[parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)][string]$TableName,
[string]$Project,
[string]$CodeLanguage,
[string[]]$TemplateFolders,
[switch]$Force = $true
)
$outputPath = "Scripts/SQL/$TableName/Delete$TableName"
Add-ProjectItemViaTemplate $outputPath -Template DeleteSQLTemplate `
-Model #{ TableName = $TableName; DatabaseName = $DatabaseName; Project = $Project } `
-SuccessMessage "Added DeleteSQL output at {0}" `
-TemplateFolders $TemplateFolders -Project $Project -CodeLanguage $CodeLanguage -Force:$Force
Write-Host "Scaffolded DeleteSQL"
And here is the T4 Template code:
<## Template Language="C#" HostSpecific="True" Inherits="DynamicTransform" Debug="True" #>
<## Output Extension="sql" #>
<## assembly name="System.Collections" #>
<## assembly name="System.Configuration" #>
<## assembly name="System.Data" #>
<## assembly name="System.Web" #>
<## import namespace="System.Collections.Generic" #>
<## import namespace="System.Configuration" #>
<## import namespace="System.Data" #>
<## import namespace="System.Data.SqlClient" #>
<## import namespace="System.Web" #>
USE [<#= Model.DatabaseName #>]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[Delete<#= Model.TableName #>] (
#P<#= Model.TableName #>ID AS BIGINT,
#PChangedByUserID BIGINT,
#PChangedByURL VARCHAR(1024),
#PChangedByIpAddress varchar(16)
)
AS
SET NOCOUNT ON
BEGIN TRY
BEGIN TRANSACTION
-- update user history
DECLARE #userHistoryID BIGINT = 0;
DECLARE #details VARCHAR(4096) = '[<#= Model.TableName #>] ID ''' + CAST(#P<#= Model.TableName #>ID AS VARCHAR) + ''' was deleted.'
EXEC InsertUserHistory #PChangedByUserID, #details, #PChangedByURL, #PChangedByIpAddress, #userHistoryID OUTPUT
-- Rollback transaction if user history was not created
IF(#userHistoryID = 0) BEGIN
ROLLBACK
SELECT CAST(-1 AS INT)
RETURN
END
DELETE FROM
[<#= Model.TableName #>]
WHERE
[ID] = #P<#= Model.TableName #>ID
COMMIT
SELECT CAST(1 AS INT)
END TRY
BEGIN CATCH
ROLLBACK
SELECT CAST(-2 AS INT)
END CATCH
RETURN
I invoke the scaffolder by typing "Scaffold DeleteSQL -DatabaseName $DatabaseName -TableName $TableName" into the Package Manager Console.
I have also tried invoking the scaffolder with the -Force option like this: "Scaffold DeleteSQL -DatabaseName $DatabaseName -TableName $TableName -Force". I have also used "-Force true" and "-Force:true" with no luck. Also notice that the $Force parameter is set to true anyway, so I think it should overwrite by default, right?
What do I need to do to get rid of this error?
Thanks for the help.
Looks like the problem had to do with the path the output was generate to.
I changed this line in the PowerShell script:
$outputPath = "Scripts/SQL/$TableName/Delete$TableName"
to this:
$outputPath = "Scripts\SQL\$TableName\Delete$TableName"
The only thing I changed was the slashes from "/" to "\". Now, I don't get the error message anymore.

connect to a database with T4?

how can I connect to a local database with T4?, I try these code but it not working
Iam using vs.net 2010 and SQL2008 with windows authentication, Iam trying to connect to my local server, to that database to get their properties
<## template language="C#v3.5" #>
<## output extension=".cs" #>
<## assembly name="System.Data" #>
<## assembly name="System.Xml" #>
<## assembly name="Microsoft.SqlServer.ConnectionInfo" #>
<## assembly name="Microsoft.SqlServer.Smo" #>
<## assembly name="Microsoft.SqlServer.Management.Sdk.Sfc" #>
<## import namespace="System.IO" #>
<## import namespace="System.Data.SqlClient" #>
<## import namespace="Microsoft.SqlServer.Management.Smo" #>
<## import namespace="Microsoft.SqlServer.Management.Common" #>
namespace T4SNUG.Entities
{
<# Server server = new Server(".");
Database db = server.Databases["Chinook"]};#>
Yes, this should work.
Are you seeing an error message or exception in the output window?
I can use the following code in a T4:
<## output extension=".txt" #>
<## assembly name="System.Data" #>
<## assembly name="System.Core.dll" #>
<## assembly name="System.Xml" #>
<## assembly name="Microsoft.SqlServer.ConnectionInfo" #>
<## assembly name="Microsoft.SqlServer.Smo" #>
<## assembly name="Microsoft.SqlServer.Management.Sdk.Sfc" #>
<## import namespace="System.IO" #>
<## import namespace="System.Linq" #>
<## import namespace="System.Data.SqlClient" #>
<## import namespace="Microsoft.SqlServer.Management.Smo" #>
<## import namespace="Microsoft.SqlServer.Management.Common" #>
<#
var server = new Server(".\\SQLEXPRESS");
var db = server.Databases["moviereviews"];
foreach(var property in db.GetType()
.GetProperties()
.Where(p=>p.PropertyType == typeof(string)))
{
#>
<#= property.Name #> : <#= property.GetValue(db, null) #>
<#
}
#>
and it gives me:
Name : moviereviews
Collation : SQL_Latin1_General_CP1_CI_AS
DatabaseSnapshotBaseName :
DefaultFileGroup : PRIMARY
DefaultFileStreamFileGroup :
DefaultFullTextCatalog :
DefaultSchema : dbo
MirroringPartner :
MirroringPartnerInstance :
MirroringWitness :
...

Resources