http://blogs.clariusconsulting.net/kzu

Daniel Cazzulino's Blog

Go Back to
kzu′s Latest post

How to transform T4 templates on build without installing a Visual Studio SDK

The MS recommended way is to just use the Microsoft.TextTemplating.targets which come with the Visual Studio Visualization and Modeling SDK. It makes me slightly nervous that it requires a little known SDK that is hosted on http://archive.msdn.microsoft.com/vsvmsdk rather than something more “official” like the MSDN Download Center, where the proper VS SDK lives. It also turns out to be absolutely unnecessary, since all you need is already installed with your base Visual Studio setup.

 

Visual Studio installs the TextTransform.exe utility,  which can use used to transform a template automatically. Ideally, you shouldn’t have to do anything different than you do today when using text templates, and the automatic build-time transform should “just happen”. One way to achieve this is by grabbing all the files that have one of the T4 custom tools assigned (they are always assigned them when you create a new text template in VS):

<Target Name="TransformOnBuild" AfterTargets="BeforeBuild">

    <Error Text="Failed to find TextTransform.exe tool at '$(_TransformExe)."
            Condition="!Exists('$(_TransformExe)')"/>

    <ItemGroup>
        <_TextTransform Include="@(None)"
                        Condition="'%(None.Generator)' == 'TextTemplatingFilePreprocessor' Or '%(None.Generator)' == 'TextTemplatingFileGenerator'" />
    </ItemGroup>

    <!-- Perform task batching for each file -->
    <Exec Command="&quot;$(_TransformExe)&quot; &quot;@(_TextTransform)&quot;"
          Condition="'%(Identity)' != ''"/>

</Target>

To determine the location of the utility, we can use a cascading mechanism where we probe for the current VS version as well as all the known ones if that doesn’t work. This is just to be on the super-safe side. Any version of the tool back to VS2010 will work just the same, since very little has changed in the T4 world since then:

<PropertyGroup>
    <!-- Initial default value -->
    <_TransformExe>$(CommonProgramFiles)\Microsoft Shared\TextTemplating\10.0\TextTransform.exe</_TransformExe>
    <!-- If explicit VS version, override default -->
    <_TransformExe Condition="'$(VisualStudioVersion)' != ''">$(CommonProgramFiles)\Microsoft Shared\TextTemplating\$(VisualStudioVersion)\TextTransform.exe</_TransformExe>
    <!-- Cascading probing if file not found -->
    <_TransformExe Condition="!Exists('$(_TransformExe)')">$(CommonProgramFiles)\Microsoft Shared\TextTemplating\10.0\TextTransform.exe"</_TransformExe>
    <_TransformExe Condition="!Exists('$(_TransformExe)')">$(CommonProgramFiles)\Microsoft Shared\TextTemplating\11.0\TextTransform.exe"</_TransformExe>
    <_TransformExe Condition="!Exists('$(_TransformExe)')">$(CommonProgramFiles)\Microsoft Shared\TextTemplating\12.0\TextTransform.exe"</_TransformExe>
    <!-- Future proof 'til VS2013+2 -->
    <_TransformExe Condition="!Exists('$(_TransformExe)')">$(CommonProgramFiles)\Microsoft Shared\TextTemplating\13.0\TextTransform.exe"</_TransformExe>
    <_TransformExe Condition="!Exists('$(_TransformExe)')">$(CommonProgramFiles)\Microsoft Shared\TextTemplating\14.0\TextTransform.exe"</_TransformExe>
</PropertyGroup>

 

That’s all that’s needed. In order to make it super-easy for myself and others, I put together a nuget package and accompanying github repo to maintain it. Just install it like:

PM> Install-Package Clarius.TransformOnBuild

You won’t need to do anything else and your text templates will start transforming automatically on build thanks to the included targets file.

 

I thought about making the target that transform smarter and only transform if the source template is changed with regards to the last generated output, but it gets very tricky if you use includes. I’m not sure if there should be an additional item property to explicitly mark an included template as the source for the change detection or if the straightforward case without includes is common enough that it’s worth including the smart check anyways despite resulting in outdated output when includes are used?

Would love to hear your thoughts on that. For the time being, simple “transform always” behavior it is :)

 

Enjoy!

Comments

13 Comments

  1. This target was just what I was looking for! This works better than using a pre-build event.
    There is some problem however, on the build server the _TransformExe get’s no value as it is looking in the “C:\Program Files\Common Files\” folder but it should (also) look in the “C:\Program Files (x86)\Common Files\” folder. After I added some extra conditions to look in that folder, it works. Could you update your package with this?
    Thanks, Marco

    • What kind of OS does your build server run?

      Since any 64bit version of Windows will define the CommonProgramFiles variables as follows:

      CommonProgramFiles = C:\Program Files (x86)\Common Files
      CommonProgramW6432 = C:\Program Files\Common Files

      So, the .targets is always looking under (x86), which is the place where VS installs the exe. Maybe VS is missing from that server? In that case, you could provide the path yourself?

      I added a $(TextTransformPath) property that you can override for that purpose in the latest version.

      Thanks!

  2. Don’t know if it helps, but the build server is a Windows Server 2008 R2 Enterprise 64-bit server, Visual Studio 2012 is installed, and TextTransform.exe is located here: “C:\Program Files (x86)\Common Files\microsoft shared\TextTemplating\11.0″
    Anyway, thanks for your quick response and I hope the new version will be available soon as an update for NuGet

    • As I mentioned on the above reply, that path is precisely right and is what $(CommonProgramFiles) resolves to. It does not resolve to the non x86.

  3. Great tool! Until now I had to use a dummy VSIX addin that I built which does nothing but triggers Build->Transform All T4 Templates before every build.

    I tried to replace my addin with your Nuget package but when I try to build my .tt files I’m getting an erro “Running transformation: System.InvalidCastException: Unable to cast object of type ‘Microsoft.VisualStudio.TextTemplating.CommandLine.CommandLineHost’ to type ‘System.IServiceProvider’.”

    I suspect it’s because the host environment is not available during transformation (I’m referencing EnvDTE.DTE in some parts of the template). The same transformation works when the above mentioned VS command is invoked before the build.

    Is this issue a scenario that simply can’t be supported, or the issue can be solved somehow? (I could provide more details about the .tt file, if needed.)

    Thanks!

  4. Hi,

    Relating to the earlier thread about CommonProgramFiles, on my Win 8.1 x64 PC, if I run SET COMMAND at a command prompt I get

    CommonProgramFiles=C:\Program Files\Common Files
    CommonProgramFiles(x86)=C:\Program Files (x86)\Common Files
    CommonProgramW6432=C:\Program Files\Common Files

    If I do the same at a command prompt specifically launched as x86 (c:\windows\syswow64\cmd.exe) then I do get the behaviour you described
    CommonProgramFiles=C:\Program Files (x86)\Common Files
    CommonProgramFiles(x86)=C:\Program Files (x86)\Common Files
    CommonProgramW6432=C:\Program Files\Common Files

    Thought this might be why you’re both seeing different behaviour. The x86/x64 redirection stuff makes sense but can trip you up if you’re not specific about the bitness of the process you’re executing.

  5. Also, I’m a big fan (and paid-up user) of the T4 editor :P Are you planning on releasing any updates?

  6. This doesn’t appear to work if your T4 template uses VS build variables, such as $(SolutionDir) or $(OutDir). Anyone have any tips on solving this problem? All of my templates have those.

  7. I have got it working using the SDK method but I was curious to try this. It won’t work for me in this case. It found the texttemplate.exe no problem but my .tt has “var path = Path.GetDirectoryName(Host.TemplateFile)+”/..//”;
    var enResxFile = Host.ResolvePath(path + “Resources.resx”);” With the SDK method it was fine but using your method I got this error when compilling:

    “Error 7 Running transformation: System.IO.DirectoryNotFoundException: Could not find a part of the path ‘E:\Resources.resx’. E:\”

    For the MSBuild method the path is resolved via Path.GetDirectoryName(Host.TemplateFile) but it seems your method by passing the .tt to
    C:\Program Files (x86)\Common Files\Microsoft Shared\TextTemplating\10.0\TextTransform.exe” “test.tt makes it not working with the path?

    To make everyone understand further. In my .tt file I have set hostspecific to true and I use Path.GetDirectoryName(Host.TemplateFile) to specify where my Resources.resx is located and then I used Host.ResovlePath to load the resx file. Any suggestion how I can solve this problem? Thanks so much in advance ;)

  8. Nice article. I do not fully understand how to use it. I believe it could help resolve an error I get when opening in VS2013 a project developed with VS2012: Could not load file or assembly ‘Microsoft.VisualStudio.TextTemplating.11.0 The system cannot find the file specified

    Could you help fix this issue? Check it online by remote desktop? For 1 hour consulting?