Announcing SevenZipCmdLine.MSBuild

This was a quick and dirty thing born out of necessity, and need to make zip files of PoshRunner so I could make its chocolatey package.

I made MSBuild tasks for creating 7zip and zip files out of the $(TargetDir) of an MSBuild project. There is a nuget package for it. Simply include it in your project via nuget and build it from the command line with the following command line:

%windir%\microsoft.net\framework\v4.0.30319\msbuild __PROJECT_FOLDER__\__PROJECT_FILE__ /t:SevenZipBin,ZipBin

This will create project.zip and project.7z in __PROJECT_FOLDER__\bin\Target. To see how to override some of the defaults, look at this msbuild file in PoshRunner.

Source code is available via a github repo, and patches are welcome!

Announcing ILRepack-BuildTasks

ILMerge is a great tool for creating a single executable out of multiple .NET assemblies. However, it has two limitations. The first is that its not open source, and you’re not supposed to include a copy in your public source code repos. The second is its an executable and therefore needs to be called from the MSBuild post build event as opposed to a proper MSBuild task. Each problem had its own mutually exclusive solution.

For the first problem, Francois Valdy (blog|twitter) wrote IL-Repack, an open source clone of ILMerge. So now I could have an exe that could be included in github repos.  This allowed my projects (specifically poshrunner.exe) to have a merge step in the postbuild. ALthough this was still a clunky batch file embedded in the csproj, it just worked.

For the second problem, Marcus Griep (blog|twitter) created ILMerge Tasks. Since the merging APIs in ILMerge are all exposed as public members, you can simply reference the exe as a dll. He did this in an MSBuild DLL. However, this dll still requires ILMerge.exe.

These solutions are no longer mutually exclusive. I’ve forked ILMerge-tasks (and contacted Marcus to see if he wants to incorporate my changes). I had it reference ILRepack. The new project is called ILRepack-BuildTasks on github. Enjoy!

YUI’s CompressorTask with MSBuild and Visual Studio

While trying to integrate YUI Compressor as a build task in my current project…

http://yuicompressor.codeplex.com/documentation

Post Build Event:

$(MSBuildBinPath)msbuild.exe "$(SolutionDir)MinificationSettings.xml" /property:SourceLocation=$(ProjectDir) /p:JavaScriptOutputFile=$(ProjectDir)ContentMinScriptsExample.min.js /p:CssOutputFile=$(ProjectDir)ContentMinCSSSite.min.css

I got the error:

MSBUILD : error MSB1008: Only one project can be specified.

C:WindowsMicrosoft.NETFrameworkv4.0.30319msbuild.exe "c:usersspadedocumentsvisual studio 2010ProjectsExampleYUICompressorTaskMinificationSettings.xml"
    /p:SourceLocation=c:usersspadedocumentsvisual studio 2010ProjectsExampleYUICompressorTaskMVCExample
    /p:JavaScriptOutputFile=c:usersspadedocumentsvisual studio 2010ProjectsExampleYUICompressorTaskMVCExampleContentMinScriptsExample.min.js
    /p:CssOutputFile=c:usersspadedocumentsvisual studio 2010ProjectsExampleYUICompressorTaskMVCExampleContentMinCSSSite.min.css

It seems that the spaces in the folder path are preventing me from enjoying YUI Compressor. Quite the same problem this person is having: MSDN Forums: Unable to correctly pass parameters to MS build. Although I suspect he gave up a bit too early.

I tried adding quotes around the value of the parameters, you would think this is going to work, it doesn’t.

Looking for a way to replace the spaces, I came across MS Build Property Functions.

With Build Property Functions, it becomes possible to run the simple replace command in all the spots where I need to, my first guess was to replace space with ‘%20’. That had mixed results, so I was left with choosing an arbitrary token for replacement.

Post Build Event:

$(MSBuildBinPath)msbuild.exe "$(SolutionDir)MinificationSettings.xml"
    /p:SourceLocation=$(ProjectDir.Replace(" ", "[[REPLACESPACE]])
    /p:JavaScriptOutputFile=$(ProjectDir.Replace(" ", "[[REPLACESPACE]])ContentMinScriptsExample.min.js
    /p:CssOutputFile=$(ProjectDir.Replace(" ", "[[REPLACESPACE]])ContentMinCSSSite.min.css

MinificationSettings.Xml:

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/MsBuild/2003">
  <UsingTask
      TaskName="CompressorTask"
      AssemblyFile="packagesYUICompressor.NET-MsBuild-Task.1.6.0.2libNET35Yahoo.Yui.Compressor.MsBuildTask.dll" />

  <PropertyGroup>
    <CssOutputFile Condition=" '$(CssOutputFile)'=='' ">SylesSheetFinal.css</CssOutputFile>
    <JavaScriptOutputFile Condition=" '$(JavaScriptOutputFile)'=='' ">JavaScriptFinal.js</JavaScriptOutputFile>
  </PropertyGroup>

  <PropertyGroup>
    <CssOutputFileFix>$(CssOutputFile.Replace('[[REPLACESPACE]]', ' '))</CssOutputFileFix>
    <JavaScriptOutputFileFix>$(JavaScriptOutputFile.Replace('[[REPLACESPACE]]', ' '))</JavaScriptOutputFileFix>
  </PropertyGroup>

  <Target Name="MyTaskTarget">
    <ItemGroup>
      
      <JavaScriptFiles Include="$(SourceLocation.Replace('[[REPLACESPACE]]', ' '))ContentScriptsExample.js"/>
	  <CssFiles Include="$(SourceLocation.Replace('[[REPLACESPACE]]', ' '))ContentCSSSite.css"/>

    </ItemGroup>
    <CompressorTask
        CssFiles="@(CssFiles)"
        DeleteCssFiles="false"
        CssOutputFile="$(CssOutputFileFix)"
        CssCompressionType="YuiStockCompression"
        JavaScriptFiles="@(JavaScriptFiles)"
        ObfuscateJavaScript="True"
        PreserveAllSemicolons="False"
        DisableOptimizations="Nope"
        EncodingType="Default"
        DeleteJavaScriptFiles="false"
        LineBreakPosition="-1"
        JavaScriptOutputFile="$(JavaScriptOutputFileFix)"
        LoggingType="ALittleBit"
        ThreadCulture="en-au"
        IsEvalIgnored="false"
            />
  </Target>
</Project>

Source Code

Using the registry to resolve Visual Studio reference paths.

Update 2012-12-24: Updated to reflect the MSBuild 4.0 syntax as I describe here.
Note: To skip the long journey of what lead me to figuring this out, click here to go to the howto.

Recently I was asked to look at a fiddler plugin Stan, the founder of this blog, was developing. He gave me a SVN path and asked me to build it and test it.

So I checked out the source code and hit F5. I got a bunch of compiler errors relating to the fact that I didn’t have fiddler installed. I rectified that matter and still got errors. The problem was that the hintpath of fiddler.exe was wrong. On my machine, Fiddler is installed in ‘C:Program FilesFiddler2’, while on Stan’s machine it is installed to ‘C:Program Files (x86)Fiddler2′. I consulted the mighty google, which led me to a StackOverflow question. The question pointed out that you can have multiple hintpaths to an assembly. However, I wanted a better solution. What if someone installed Fiddler to a custom location?

I got the idea of using the registry. Fiddler has an installer. Surely the installer records its install location to the registry. It does in ‘HKEY_LOCAL_MACHINESOFTWAREMicrosoftFiddler2’ (Apparently fiddler is written by a Microsoft Employee). So the question is how to get MSBuild, the tool that visual studio uses to parse project files, to read a value from the registry.

The answer was found in a post on the MSBuild team blog. However, for Visual Studio 2010 and later, I recommend the syntax described here. The new syntax takes into account RegistryView and the Wow6432Node. The old syntax happens to work just fine in Visual Studio because Fiddler is compiled to 32 bits explicitly and Visual Studio is a 32 bit app. However, if you were to compile the app on the command line on a 64 bit system, the reference would not be resolved.

How To

Unfortunately, Visual Studio does not allow you to edit hintpaths to referenced assemblies. So you’re going to have to edit your vcproj or vbproj file in notepad or some other text editor. Here are the steps:

  1. Open the project file in your text editor.
  2. Look for the element for fiddler.exe. It should look similar to this:
    <Reference Include=”Fiddler, Version=2.2.7.5, Culture=neutral, processorArchitecture=MSIL”>
      <SpecificVersion>False</SpecificVersion>
      <HintPath>C:\Program Files\Fiddler.exe</HintPath>
      <Private>False</Private>
      </Reference>
  3. Change the hintpath as follows (Visual Studio 2010 and later):
    <Reference Include="Fiddler, Version=2.2.7.5, Culture=neutral, processorArchitecture=MSIL">
    <SpecificVersion>False</SpecificVersion>
      <HintPath>$(Registry:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Fiddler2@InstallPath)Fiddler.exe</HintPath>
      <Private>False</Private>
    </Reference>
  4. On Visual Studio 2008, you must use the old syntax:
    <Reference Include="Fiddler, Version=2.2.7.5, Culture=neutral, processorArchitecture=MSIL">
      <SpecificVersion>False</SpecificVersion>
      <HintPath>$([MSBuild]::GetRegistryValueFromView('HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Fiddler2', 'InstallPath', null, RegistryView.Registry32))Fiddler.exe</HintPath>
    <Private>False</Private>
    </Reference>
  5. Save the file
  6. Visual studio will detect the file change and ask you to reload the file. If you are using SharpDevelop as you’re IDE, you will have to close and reopen the solution.

Thats all there is to it. Happy coding!