A blog about SQL Server, SSIS, C# and whatever else I happen to be dealing with in my professional life.

Find ramblings

Showing posts with label Visual Studio. Show all posts
Showing posts with label Visual Studio. Show all posts

Wednesday, February 25, 2015

Biml - Snippets

I've been working with Biml for a year and a half now. With the Intellisense built into BIDS Helper or Mist itself, I can bang out some code fairly quick. My mental parser isn't too bad either, I can read and generally see what is/isn't set correctly in it. Except for the Script Tasks and Components. Even in Mist, they still kick me in the pants. What's the ProjectCoreName and how does that differ from the ScriptTaskProjectName and should it differ? What's the crazy syntax for escaping my code within the code? Yeah, I don't care anymore.

I no longer care, because I have a snippet. If only I had a Donk! A snippet is like a macro — type some mnemonic keystroke and if you want the snippet, hit Tab. The C# snippet that comes to mind is cw which autocompletes to Console.WriteLine. Man, that'd be helpful for slingin' Biml.

Wait, why haven't I used them? I know you can create custom snippets for .NET so why not one for "XML?" Yeah self, why not? To save you the trouble of arguing with yourself for not being clever, I'm going to tell you to start creating your own snippets, contribute them to bimlscript.com and let's get cranking.

Getting started with snippets

I don't know that you have to, but there's a very handy tool called Snippet Designer that makes it a cinch to create snippets.

Highlight the text you're interested in and in your right click menu, Export as Snippet. You can ignore the Create Snippet..., that's Red Gate's SQL Prompt and won't create the right type of snippet for these file types.

For a script task, I'm just going to assume I'm starting with an brand new Biml file so I've selected everything but that and put it into a snippet. You'll then be presented with a nice little editor so you can use things like anchors and such which I made heavy use of in TextPad's clip library.

I save it out and it goes into a file called C:\Users\bfellows\Documents\Visual Studio 2012\Code Snippets\XML\My Xml Snippets\ScriptTaskCS_2012.snippet

<?xml version="1.0" encoding="utf-8"?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <CodeSnippet Format="1.0.0">
    <Header>
      <SnippetTypes>
        <SnippetType>Expansion</SnippetType>
      </SnippetTypes>
      <Title>ScriptTaskCS_2012</Title>
      <Author>admin</Author>
      <Description>
      </Description>
      <HelpUrl>
      </HelpUrl>
      <Shortcut>
      </Shortcut>
    </Header>
    <Snippet>
      <Code Language="xml"><![CDATA[    <ScriptProjects>
        <ScriptTaskProject ProjectCoreName="ST_12345" Name="ST_12345" VstaMajorVersion="0">
            <ReadOnlyVariables>
                <Variable Namespace="System" VariableName="MachineName" DataType="Boolean" />
            </ReadOnlyVariables>
            <Files>
                <File Path="ScriptMain.cs" BuildAction="Compile">using System;
using System.Data;
using Microsoft.SqlServer.Dts.Runtime;
using System.Windows.Forms;

namespace ST_12345
{
    [Microsoft.SqlServer.Dts.Tasks.ScriptTask.SSISScriptTaskEntryPointAttribute]
    public partial class ScriptMain : Microsoft.SqlServer.Dts.Tasks.ScriptTask.VSTARTScriptObjectModelBase
    {
        public void Main()
        {
            bool fireAgain = false;
            string message = Dts.Variables["System::MachineName"].Value.ToString();
            Dts.Events.FireInformation(0, "Log MachineName", message, string.Empty, 0, ref fireAgain);

            Dts.TaskResult = (int)ScriptResults.Success;
        }

        enum ScriptResults
        {
            Success = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Success,
            Failure = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Failure
        };
    }
}                </File>
                <File Path="Properties\AssemblyInfo.cs" BuildAction="Compile">
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("AssemblyTitle")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("I <3 @billinkc")]
[assembly: AssemblyProduct("ProductName")]
[assembly: AssemblyCopyright("Copyright @  2015")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
//
// Version information for an assembly consists of the following four values:
//
//      Major Version
//      Minor Version
//      Build Number
//      Revision
//
// You can specify all the values or you can default the Revision and Build Numbers
// by using the '*' as shown below:

[assembly: AssemblyVersion("1.0.*")]
                </File>
            </Files>
            <AssemblyReferences>
                <AssemblyReference AssemblyPath="System" />
                <AssemblyReference AssemblyPath="System.Data" />
                <AssemblyReference AssemblyPath="System.Windows.Forms" />
                <AssemblyReference AssemblyPath="System.Xml" />
                <AssemblyReference AssemblyPath="Microsoft.SqlServer.ManagedDTS.dll" />
                <AssemblyReference AssemblyPath="Microsoft.SqlServer.ScriptTask.dll" />
            </AssemblyReferences>
        </ScriptTaskProject>
    </ScriptProjects>
    <Packages>
        <Package Name="BasicScriptTask" ConstraintMode="Linear">
            <Tasks>
                <Script ProjectCoreName="ST_12345" Name="SCR Do Stuff">
                    <ScriptTaskProjectReference ScriptTaskProjectName="ST_12345" />
                </Script>
            </Tasks>
        </Package>
    </Packages>]]></Code>
    </Snippet>
  </CodeSnippet>
</CodeSnippets>
Now when I need a script task, my workflow is
  1. Add new biml file
  2. Right click, Insert Snippet (Ctrl-K, Ctrl-X)
  3. Navigate to My Xml Snippets, select ScriptTaskCS_2012
  4. Replace the Name attribute for Package and replace all instances of ST_1235 with something a little more unique

If I click Generate SSIS Package, the biml engine is going to fire up and emit an SSIS package with a script task. How cool is that? Think about how you can leverage snippets and CallBimlScript: Replicate-o-matic, Don't Repeat Your Biml, Callable BimlScript (Caller), etc.

Like this? Joost van Rossum (b|t) just posted Creating BIML Script Component Transformation (rownumber). That's your framework for creating a Script Component, acting as a transform. Add that to your Snippets and now you have an example of each.

For large libraries, you might want to make those CoreNames unique. I'll see if there's an API call for generating a unique name. Also, you can make this into an in-line project script as Scott shows on Creating Script Task Projects inline. The difference between the two approaches boils down to do you want to create shareable, project level tasks or per-package tasks.

I am very excited about integrating snippets into my biml workflow and I hope this has opened your eyes to another means for speeding your development.

Wednesday, February 18, 2015

Slimming down the SSIS Script Task

SSIS Script Task History

Gather 'round children, I want to tell you a tale of woe. The 2005 release of SQL Server Integration Services allowed you to use any .NET language you wanted in a Script Task or Script Component, as long as you liked Visual Basic .NET. The 2008 release of SSIS allowed us to use either "Microsoft Visual C# 2008" or "Microsoft Visual Basic 2008". Many .NET devs rejoiced over this and that's what today's post is about.

This, is what the standard Task would generate as template code.

/*
   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;
using System.Windows.Forms;

namespace ST_d80f6050516944fa8639234f7b2e50b9.csproj
{
    [System.AddIn.AddIn("ScriptMain", Version = "1.0", Publisher = "", Description = "")]
    public partial class ScriptMain : Microsoft.SqlServer.Dts.Tasks.ScriptTask.VSTARTScriptObjectModelBase
    {

        #region VSTA generated code
        enum ScriptResults
        {
            Success = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Success,
            Failure = Microsoft.SqlServer.Dts.Runtime.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
            Dts.TaskResult = (int)ScriptResults.Success;
        }
    }
}
That's 50 lines in total, my trim job brings that down to 23.

I hate regions. Hate them with the fury of a thousand suns. Ctrl-M, Ctrl-P stops all outlining but I have to click that every time I open the script, or I have to remove the stupid #region lines. That's a minor annoyance but one I lived through.

The 2012/2014 release of SSIS was designed to make it easier for people to get started. We had these getting started videos that were suggested every time you create a new integration services project which is really charming when your job is an ETL developer. As part of the rookie developer changes, the default Script Task now provides you with a lot more hand holding with regard to developing your first Task. The following is that template

#region Help:  Introduction to the script task
/* The Script Task allows you to perform virtually any operation that can be accomplished in
 * a .Net application within the context of an Integration Services control flow. 
 * 
 * Expand the other regions which have "Help" prefixes for examples of specific ways to use
 * Integration Services features within this script task. */
#endregion


#region Namespaces
using System;
using System.Data;
using Microsoft.SqlServer.Dts.Runtime;
using System.Windows.Forms;
#endregion

namespace ST_12345
{
    /// <summary>
    /// ScriptMain is the entry point class of the script.  Do not change the name, attributes,
    /// or parent of this class.
    /// </summary>
    [Microsoft.SqlServer.Dts.Tasks.ScriptTask.SSISScriptTaskEntryPointAttribute]
    public partial class ScriptMain : Microsoft.SqlServer.Dts.Tasks.ScriptTask.VSTARTScriptObjectModelBase
    {
        #region Help:  Using Integration Services variables and parameters in a script
        /* To use a variable in this script, first ensure that the variable has been added to 
         * either the list contained in the ReadOnlyVariables property or the list contained in 
         * the ReadWriteVariables property of this script task, according to whether or not your
         * code needs to write to the variable.  To add the variable, save this script, close this instance of
         * Visual Studio, and update the ReadOnlyVariables and 
         * ReadWriteVariables properties in the Script Transformation Editor window.
         * To use a parameter in this script, follow the same steps. Parameters are always read-only.
         * 
         * Example of reading from a variable:
         *  DateTime startTime = (DateTime) Dts.Variables["System::StartTime"].Value;
         * 
         * Example of writing to a variable:
         *  Dts.Variables["User::myStringVariable"].Value = "new value";
         * 
         * Example of reading from a package parameter:
         *  int batchId = (int) Dts.Variables["$Package::batchId"].Value;
         *  
         * Example of reading from a project parameter:
         *  int batchId = (int) Dts.Variables["$Project::batchId"].Value;
         * 
         * Example of reading from a sensitive project parameter:
         *  int batchId = (int) Dts.Variables["$Project::batchId"].GetSensitiveValue();
         * */

        #endregion

        #region Help:  Firing Integration Services events from a script
        /* This script task can fire events for logging purposes.
         * 
         * Example of firing an error event:
         *  Dts.Events.FireError(18, "Process Values", "Bad value", "", 0);
         * 
         * Example of firing an information event:
         *  Dts.Events.FireInformation(3, "Process Values", "Processing has started", "", 0, ref fireAgain)
         * 
         * Example of firing a warning event:
         *  Dts.Events.FireWarning(14, "Process Values", "No values received for input", "", 0);
         * */
        #endregion

        #region Help:  Using Integration Services connection managers in a script
        /* Some types of connection managers can be used in this script task.  See the topic 
         * "Working with Connection Managers Programatically" for details.
         * 
         * Example of using an ADO.Net connection manager:
         *  object rawConnection = Dts.Connections["Sales DB"].AcquireConnection(Dts.Transaction);
         *  SqlConnection myADONETConnection = (SqlConnection)rawConnection;
         *  //Use the connection in some code here, then release the connection
         *  Dts.Connections["Sales DB"].ReleaseConnection(rawConnection);
         *
         * Example of using a File connection manager
         *  object rawConnection = Dts.Connections["Prices.zip"].AcquireConnection(Dts.Transaction);
         *  string filePath = (string)rawConnection;
         *  //Use the connection in some code here, then release the connection
         *  Dts.Connections["Prices.zip"].ReleaseConnection(rawConnection);
         * */
        #endregion


        /// <summary>
        /// This method is called when this script task executes in the control flow.
        /// Before returning from this method, set the value of Dts.TaskResult to indicate success or failure.
        /// To open Help, press F1.
        /// </summary>
        public void Main()
        {
            // TODO: Add your code here

            Dts.TaskResult = (int)ScriptResults.Success;
        }

        #region ScriptResults declaration
        /// <summary>
        /// This enum provides a convenient shorthand within the scope of this class for setting the
        /// result of the script.
        /// 
        /// This code was generated automatically.
        /// </summary>
        enum ScriptResults
        {
            Success = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Success,
            Failure = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Failure
        };
        #endregion

    }
}

Mercy, I get all twitchy in the eye just looking at it. That is 113 lines of code and for those that really pay attention to such things, there's a delightful mix of tabs and space characters. Again, after I go through my defluffing process, I'm back to 23 lines. But, the stripping process is much slower. I have more regions to deal with and inaccurate comments to clean and it's just a lot more work than my OCD brain should have to expend.

There must be a way to take the training wheels off a Script Task. Today I found that switch. Meet the ProjectTemplatePath property. On my 2012 installation, it points to C:\Program Files (x86)\Microsoft SQL Server\110\DTS\Binn\VSTA11_IS_ST_CS_Template.vstax

VSTA11_IS_ST_CS_Template.vstax

In fine Microsoft tradition, the vstax file is a ... what? Anyone? This fine document states An Open Packaging Container (OPC) file that contains one or more project templates which to me says "a zip file." So, I copied that out of my installation location and unzipped it. 10 files
  • AssemblyInfo.cs
  • IS%20Script%20Task%20Project.csproj
  • Resources.Designer.cs
  • Resources.resx
  • ScriptMain.cs
  • Settings.Designer.cs
  • Settings.settings
  • VSTA11_IS_ST_CS_Template.vstatemplate
  • vstax.manifest
  • [Content_Types].xml
Oh, how lovely! Point your favourite text editor at ScriptMain.cs. Look familiar? That is your ScriptMain, except it has a token of $safeprojectname$ instead of ST_12345 for the namespace.

There be dragons here

What we're about to do has the possibility of breaking your Visual Studio/BIDS/SSDT installation and you should not do it.

Really, by mucking about you and you alone are responsible for your actions. You break it, you fix it.

Excellent, you're still here. Step 1. Edit the contents of ScriptMain.cs as you see fit. Mine looks like the following.

using System;
using System.Data;
using Microsoft.SqlServer.Dts.Runtime;
using System.Windows.Forms;

namespace $safeprojectname$
{
    [Microsoft.SqlServer.Dts.Tasks.ScriptTask.SSISScriptTaskEntryPointAttribute]
    public partial class ScriptMain : Microsoft.SqlServer.Dts.Tasks.ScriptTask.VSTARTScriptObjectModelBase
    {
        public void Main()
        {
            Dts.TaskResult = (int)ScriptResults.Success;
        }

        enum ScriptResults
        {
            Success = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Success,
            Failure = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Failure
        };

    }
}
I do comment my code, usually to the level that F/X Cop stops barking at me but I don't want to start with the annoying boilerplate comments.

Step 2 Create a new version of the vstax file. I could not figure out how to make the native windows compressed folder thing to not compress the archive. 7-Zip however makes this a snap.

  1. Select all 10 files in the folder. Do not select the enclosing folder or your archive will be wrong
  2. Right click and choose the Add to Archive option
  3. Change your "Archive format" to zip and the "Compression level" to Store
  4. Give the archive a name like VSTA11_IS_ST_CS_Template.vstax
If the resulting zip is approximately 8KB, you have compression turned on. This won't cause SSDT to break, but it also won't be able to instantiate the Task editor until you fix it.

Step 3 Backup. Make a copy of your existing VSTA11_IS_ST_CS_Template.vstax file in the Binn folder. Keep this safe.

Step 4 Replace the vstax file in the Binn folder with the one you just created. You will be prompted to perform an admin task with this since it's a protected folder. I clicked yes, but you shouldn't because you machine may catch on fire.

Step 5 Test. You don't even have to restart Visual Studio. Just drag a new Script Task onto your canvas and click edit script. If you've done everything correctly, you'll be sporting a slimmer Script Task. If this doesn't work, then replace your modified vstax file with the copy you made in Step 3.

Wednesday, March 10, 2010

Post-build event

I was looking for an article I had seen that discussed how to use the installer project to simplify my SSIS component deployment and I ran across Todd McDermid's article The Post-Build Command Line for SSIS Custom Objects (Updated)

That's pretty.  Way more elegant and reusable than what we are doing here http://billfellows.blogspot.com/2009/09/visual-studio-build-macros.html We just copy the DLL into PipelineComponents and Tasks, regardless of what it is.  However, the batch script failed on a default machine because gacutil is not part of the standard path.  Sure, you can add it in there. Or you can add some guessing logic into those scripts.  I went with the later.

Using the files on Todd's Skydrive, I added my sauce.  I suspect using $(FrameworkSDKDir) will provide the most consistent reference for gacutil which is why it's listed last

SET GACUtil=unset

REM Not sure why one would choose windir vs SystemRoot
REM both seem to evaluate to same value on available systems
SET GACUTIL_PATH_win1="%windir%\system32\dllcache\gacutil.exe"
SET GACUTIL_PATH_win2="%windir%\gacutil.exe"
SET GACUTIL_PATH_v1="%windir%\Microsoft.NET\Framework\v1.1.4322\gacutil.exe"
SET GACUTIL_PATH_v2005="%ProgramFiles%\Microsoft Visual Studio 8\SDK\v2.0\Bin\gacutil.exe"
SET GACUTIL_PATH_v2008="%ProgramFiles%\Microsoft SDKs\Windows\v6.0A\Bin\gacutil.exe"
SET GACUTIL_PATH_v2008a="%ProgramFiles%\Microsoft SDKs\Windows\v6.0A\Bin\gacutil.exe"
SET GACUTIL_PATH_framework="$(FrameworkSDKDir)bin\gacutil.exe"

IF EXIST %GACUTIL_PATH_win1% SET GACUtil=%GACUTIL_PATH_win1%
IF EXIST %GACUTIL_PATH_win2% SET GACUtil=%GACUTIL_PATH_win2%
IF EXIST %GACUTIL_PATH_v1% SET GACUtil=%GACUTIL_PATH_v1%
IF EXIST %GACUTIL_PATH_v2005% SET GACUtil=%GACUTIL_PATH_v2005%
IF EXIST %GACUTIL_PATH_v2008% SET GACUtil=%GACUTIL_PATH_v2008%
IF EXIST %GACUTIL_PATH_v2008a% SET GACUtil=%GACUTIL_PATH_v2008a%
IF EXIST %GACUTIL_PATH_framework% SET GACUtil=%GACUTIL_PATH_framework%

I then replaced all mentions of GACUtil with %GACUtil%  and voilĂ !

Monday, September 14, 2009

Visual studio build macros

We defined Post-build events for one of our SSIS components .NET projects. Nothing terribly fancy, it just registers the signed assembly in the GAC and pushes it to the Tasks and Pipleine folders for use in the toolbox. As part of our migration from VS 2005 to 2008, I had to rework the events as they were hard coded for 05.

If one right clicks on a project, you can define your pre and post build events. The links below further link to more information if that doesn't give you enough to go on. My challenge was given all the possible macros, which ones did I want? Was it DevEnvDir, FrameworkDir, or FrameworkSDKDir. In the event builder dialogue, there is a Macros >> which results in the following but it only lists what is defined

Sample values for Macros for Build Commands and Properties Your
mileage may vary

http://msdn.microsoft.com/en-us/library/c02as0cs(VS.71).aspx
http://msdn.microsoft.com/en-us/library/c02as0cs.aspx

This was our original post-build scneario. It would force the custom component dll to be registered with the GAC. That DLL contains both data flow task components as well as control flow items so for developers to use it, it needs to exist in the PipelineComponents folder as well as the Tasks folder. We copy it to the Framework folder so it can be picked up for Script tasks and finally, we copy push a template package into the VS folder so it shows as template. As part of the migration from SQL Server 2005 to 2008, the only real change we had to make from a post-build perspective was to make the 90 folders 100. However, the virtual I was working on didn't have gacutil in the path. It seemed silly to update the path to navigate to the executable and so I started digging through the available macros



gacutil -iF "$(TargetPath)"
copy /y "$(TargetPath)" "$(ProgramFiles)\Microsoft SQL Server\90\DTS\PipelineComponents"
copy /y "$(TargetPath)" "$(ProgramFiles)\Microsoft SQL Server\90\DTS\Tasks"
copy /y "$(TargetPath)" "C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727"
copy /y "$(SolutionDir)\SQL\SSIS\PackageTemplate.dtsx" "$(DevEnvDir)\PrivateAssemblies\ProjectItems\DataTransformationProject\DataTransformationItems"

becomes

"$(FrameworkSDKDir)bin\gacutil.exe" -u "$(TargetName)"
"$(FrameworkSDKDir)bin\gacutil.exe" -i "$(TargetFileName)"
copy /y "$(TargetPath)" "$(ProgramFiles)\Microsoft SQL Server\100\DTS\PipelineComponents"
copy /y "$(TargetPath)" "$(ProgramFiles)\Microsoft SQL Server\100\DTS\Tasks"
copy /y "$(TargetPath)" "C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727"
copy /y "$(SolutionDir)\SQL\SSIS\PackageTemplate.dtsx" "$(DevEnvDir)\PrivateAssemblies\ProjectItems\DataTransformationProject\DataTransformationItems"


Using this as my Post-Event scenario

echo RemoteMachine = $(RemoteMachine)
echo References = $(References)
echo ConfigurationName = $(ConfigurationName)
echo PlatformName = $(PlatformName)
echo Inherit = $(Inherit)
echo NoInherit = $(NoInherit)
echo StopEvaluating = $(StopEvaluating)
echo ParentName = $(ParentName)
echo RootNameSpace = $(RootNameSpace)
echo IntDir = $(IntDir)
echo OutDir = $(OutDir)
echo DevEnvDir = $(DevEnvDir)
echo InputDir = $(InputDir)
echo InputPath = $(InputPath)
echo InputName = $(InputName)
echo InputFileName = $(InputFileName)
echo InputExt = $(InputExt)
echo ProjectDir = $(ProjectDir)
echo ProjectPath = $(ProjectPath)
echo ProjectName = $(ProjectName)
echo ProjectFileName = $(ProjectFileName)
echo ProjectExt = $(ProjectExt)
echo SolutionDir = $(SolutionDir)
echo SolutionPath = $(SolutionPath)
echo SolutionName = $(SolutionName)
echo SolutionFileName = $(SolutionFileName)
echo SolutionExt = $(SolutionExt)
echo TargetDir = $(TargetDir)
echo TargetPath = $(TargetPath)
echo TargetName = $(TargetName)
echo TargetFileName = $(TargetFileName)
echo TargetExt = $(TargetExt)
echo VSInstallDir = $(VSInstallDir)
echo VCInstallDir = $(VCInstallDir)
echo FrameworkDir = $(FrameworkDir)
echo FrameworkVersion = $(FrameworkVersion)
echo FrameworkSDKDir = $(FrameworkSDKDir)
echo WebDeployPath = $(WebDeployPath)
echo WebDeployRoot = $(WebDeployRoot)
echo SafeParentName = $(SafeParentName)
echo SafeInputName = $(SafeInputName)
echo SafeRootNamespace = $(SafeRootNamespace)
echo FxCopDir = $(FxCopDir)

generates the following


------ Build started: Project: MacroExposition, Configuration: Debug
Any CPU ------
MacroExposition ->
C:\sandbox\MacroExposition\MacroExposition\bin\Debug\MacroExposition.exe
echo RemoteMachine =
echo References =
echo ConfigurationName = Debug
echo PlatformName = AnyCPU
echo Inherit =
echo NoInherit =
echo StopEvaluating =
echo ParentName =
echo RootNameSpace = MacroExposition
echo IntDir =
echo OutDir = bin\Debug\
echo DevEnvDir = C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\
echo InputDir =
echo InputPath =
echo InputName =
echo InputFileName =
echo InputExt =
echo ProjectDir = C:\sandbox\MacroExposition\MacroExposition\
echo ProjectPath =
C:\sandbox\MacroExposition\MacroExposition\MacroExposition.csproj
echo ProjectName = MacroExposition
echo ProjectFileName = MacroExposition.csproj
echo ProjectExt = .csproj
echo SolutionDir = C:\sandbox\MacroExposition\
echo SolutionPath = C:\sandbox\MacroExposition\MacroExposition.sln
echo SolutionName = MacroExposition
echo SolutionFileName = MacroExposition.sln
echo SolutionExt = .sln
echo TargetDir = C:\sandbox\MacroExposition\MacroExposition\bin\Debug\
echo TargetPath =
C:\sandbox\MacroExposition\MacroExposition\bin\Debug\MacroExposition.exe
echo TargetName = MacroExposition
echo TargetFileName = MacroExposition.exe
echo TargetExt = .exe
echo VSInstallDir =
echo VCInstallDir =
echo FrameworkDir = c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727
echo FrameworkVersion =
echo FrameworkSDKDir = C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\
echo WebDeployPath =
echo WebDeployRoot =
echo SafeParentName =
echo SafeInputName =
echo SafeRootNamespace =
echo FxCopDir =


RemoteMachine =
References =
ConfigurationName = Debug
PlatformName = AnyCPU
Inherit =
NoInherit =
StopEvaluating =
ParentName =
RootNameSpace = MacroExposition
IntDir =
OutDir = bin\Debug\
DevEnvDir = C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\
InputDir =
InputPath =
InputName =
InputFileName =
InputExt =
ProjectDir = C:\sandbox\MacroExposition\MacroExposition\
ProjectPath = C:\sandbox\MacroExposition\MacroExposition\MacroExposition.csproj
ProjectName = MacroExposition
ProjectFileName = MacroExposition.csproj
ProjectExt = .csproj
SolutionDir = C:\sandbox\MacroExposition\
SolutionPath = C:\sandbox\MacroExposition\MacroExposition.sln
SolutionName = MacroExposition
SolutionFileName = MacroExposition.sln
SolutionExt = .sln
TargetDir = C:\sandbox\MacroExposition\MacroExposition\bin\Debug\
TargetPath = C:\sandbox\MacroExposition\MacroExposition\bin\Debug\MacroExposition.exe
TargetName = MacroExposition
TargetFileName = MacroExposition.exe
TargetExt = .exe
VSInstallDir =
VCInstallDir =
FrameworkDir = c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727
FrameworkVersion =
FrameworkSDKDir = C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\
WebDeployPath =
WebDeployRoot =
SafeParentName =
SafeInputName =
SafeRootNamespace =
FxCopDir =
========== Build: 1 succeeded or up-to-date, 0 failed, 0 skipped ==========

And for what it's worth, this is what the virtual kicked out

echo RemoteMachine = 
echo References =
echo ConfigurationName = Debug
echo PlatformName = AnyCPU
echo Inherit =
echo NoInherit =
echo StopEvaluating =
echo ParentName =
echo RootNameSpace = WR.Common.SSIS
echo IntDir =
echo OutDir = .\bin\Debug\
echo DevEnvDir = D:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\
echo InputDir =
echo InputPath =
echo InputName =
echo InputFileName =
echo InputExt =
echo ProjectDir = C:\Src\SalesReporting\Src\CommonSSIS\
echo ProjectPath = C:\Src\SalesReporting\Src\CommonSSIS\CommonSSIS.csproj
echo ProjectName = CommonSSIS
echo ProjectFileName = CommonSSIS.csproj
echo ProjectExt = .csproj
echo SolutionDir = C:\src\SalesReporting\Src\CommonSSIS\
echo SolutionPath = C:\src\SalesReporting\Src\CommonSSIS\CommonSSIS.sln
echo SolutionName = CommonSSIS
echo SolutionFileName = CommonSSIS.sln
echo SolutionExt = .sln
echo TargetDir = C:\Src\SalesReporting\Src\CommonSSIS\bin\Debug\
echo TargetPath = C:\Src\SalesReporting\Src\CommonSSIS\bin\Debug\WRCommonSSIS.dll
echo TargetName = WRCommonSSIS
echo TargetFileName = WRCommonSSIS.dll
echo TargetExt = .dll
echo VSInstallDir =
echo VCInstallDir =
echo FrameworkDir = C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727
echo FrameworkVersion =
echo FrameworkSDKDir = C:\Program Files\Microsoft SDKs\Windows\v6.0A\
echo WebDeployPath =
echo WebDeployRoot =
echo SafeParentName =
echo SafeInputName =
echo SafeRootNamespace =
echo FxCopDir =
RemoteMachine =
References =
ConfigurationName = Debug
PlatformName = AnyCPU
Inherit =
NoInherit =
StopEvaluating =
ParentName =
RootNameSpace = WR.Common.SSIS
IntDir =
OutDir = .\bin\Debug\
DevEnvDir = D:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\
InputDir =
InputPath =
InputName =
InputFileName =
InputExt =
ProjectDir = C:\Src\SalesReporting\Src\CommonSSIS\
ProjectPath = C:\Src\SalesReporting\Src\CommonSSIS\CommonSSIS.csproj
ProjectName = CommonSSIS
ProjectFileName = CommonSSIS.csproj
ProjectExt = .csproj
SolutionDir = C:\src\SalesReporting\Src\CommonSSIS\
SolutionPath = C:\src\SalesReporting\Src\CommonSSIS\CommonSSIS.sln
SolutionName = CommonSSIS
SolutionFileName = CommonSSIS.sln
SolutionExt = .sln
TargetDir = C:\Src\SalesReporting\Src\CommonSSIS\bin\Debug\
TargetPath = C:\Src\SalesReporting\Src\CommonSSIS\bin\Debug\WRCommonSSIS.dll
TargetName = WRCommonSSIS
TargetFileName = WRCommonSSIS.dll
TargetExt = .dll
VSInstallDir =
VCInstallDir =
FrameworkDir = C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727
FrameworkVersion =
FrameworkSDKDir = C:\Program Files\Microsoft SDKs\Windows\v6.0A\
WebDeployPath =
WebDeployRoot =
SafeParentName =
SafeInputName =
SafeRootNamespace =
FxCopDir =

Wednesday, May 27, 2009

Strip trailing spaces on save in Visual Studio

My preferred windows text editor is TextPad. It's rock solid, the keystrokes were intuitive coming from vi, allows regular expressions in search and replac and allows for the definition of custom classes. One of the features I love is its ability to strip trailing whitespace
when you save a file. It's just an OCD trait that for code files, I only want what needs to be in there.

Assuming it's always been done, it makes code diffs easier, takes up less space, etc, etc. Historical arguments, I'm sure people will argue. Diff tools exist that ignore differences in whitespace. While it's entirely true, sometimes I've only had rudimentary tools like windows file compare (fc). Space on disk for code files hasn't been an issue for eons but for the want of a nail[1], the kingdom was lost.

Today, a small measure of happiness entered my life when I found the posting by Dyaus at http://stackoverflow.com/questions/82971/how-to-automatically-remove-trailing-whitespace-in-visual-studio-2008/83043 They had a macro that runs after save to strip trailing whitespace and it works like a champ thus far. Although the question was for VS 2008, I'm running it on 2005 without an issue. Reproduced in case the link goes dead

' Add the following into the EnvironmentEvents Module for your macros
Private saved As Boolean = False
Private Sub DocumentEvents_DocumentSaved(ByVal document As EnvDTE.Document) _
Handles DocumentEvents.DocumentSaved
If Not saved Then
Try
DTE.Find.FindReplace(vsFindAction.vsFindActionReplaceAll, _
"\t", _
vsFindOptions.vsFindOptionsRegularExpression, _
"  ", _
vsFindTarget.vsFindTargetCurrentDocument, , , _
vsFindResultsLocation.vsFindResultsNone)

' Remove all the trailing whitespaces.
DTE.Find.FindReplace(vsFindAction.vsFindActionReplaceAll, _
":Zs+$", _
vsFindOptions.vsFindOptionsRegularExpression, _
String.Empty, _
vsFindTarget.vsFindTargetCurrentDocument, , , _
vsFindResultsLocation.vsFindResultsNone)

saved = True
document.Save()
Catch ex As Exception
MsgBox(ex.Message, MsgBoxStyle.OkOnly, "Trim White Space exception")
End Try
Else
saved = False
End If
End Sub



[1] http://en.wikipedia.org/wiki/For_Want_of_a_Nail