Wednesday, March 23, 2016

Calling Powershell script in TFS 2013 Build Definition

Overview
The Build Definition Default template in TFS 2013 has added the ability to execute Powershell scripts before and after a build (Pre-build and Post-Build). This is different than the pre/post build scripts in Visual Studio. This is much easier than customizing the build template as was required in earlier version of TFS. TFS 2015 is different again, but I believe will still use Powershell scripts for extending its functionality.

Your Powershell Script
You will need to put the Powershell script in a file (a good thing) and reference it in the Pre-build script path or Post-build script path properties of the Process | Build | Advanced screen. There are corresponding Pre-build script agruments and Post-build script arguments properties as well that allow parameters to be passed to the Powershell scripts.

To avoid additional issues or configuration you may want to keep the file in the same path in TFS Source Control. If you don't want or need it to be under source control you can put it directly on the TFS Controller server and reference that path.This can be notably easier for experimenting, but you'll lose the benefits of source control unless manually keep changes in source control.

Logging 
Shows up under the Diagnostics tab (TFS web | your TFS project | Build tab | your build definition | Diagnostics tab)
Write-Host
Write-Output
Write-Warning
Write-Verbose

NOTE: If you want the Write-Verbose calls to show up you need to add [CmdletBinding()] attribute to the beginning of your .ps1 file AND add -verbose to the Pre-Build script agruments property in the build definition.

Environment Variables

  • $env.TF_BUILD_BUILDDEFINITIONNAME
  • $env.TF_BUILD_BUILDDIRECTORY
  • $env.TF_BUILD_BUILDNUMBER
  • $env.TF_BUILD_BUILDREASON
  • $env.TF_BUILD_BUILDURI
  • $env.TF_BUILD_DROPLOCATION
  • $env.TF_BUILD_SOURCEGETVERSION
  • $env.TF_BUILD_SOURCESDIRECTORY
  • $env.TF_BUILD_TESTRESULTSDIRECTORY
You can also put dir anywhere in the Powershell script to see all the environment varialbes.

NOTE: Much of this information is actually gathered from here.
Good information specific to TFS2013 can be found here.

Friday, March 11, 2016

Search and Replace in a MS Word document

I reviewed options for editing MS Word files in DOCX format and decided that the free and one that has the greatest chance of existing in 10 years is just using the Open XML Word Processing SDK (see my review for details).

For complex stuff maybe a different choice would make sense. However, my requirements are simple much like a mail merge:

  1. Taking an existing MS Word file (DOCX format) as input. (Use it as a template)
  2. Search the MS Word file for some placeholders / tags and replace with real data, but don't save any changes to the original file since it is my template.
  3. Be able to write changes to a new file or stream file to browser for download
As it turns out this can be done in very few lines of code and for FREE. Below is my solution.

// Sample command line application
static void Main(string[] args)
{
    string filename = "Test.docx";
    var oldNewValues = new Dictionary<string, string>();
    oldNewValues.Add("pear", "banana");
    oldNewValues.Add("love", "like");
    byte[] returnedBytes = SearchAndReplace(filename, oldNewValues);
    File.WriteAllBytes("Changed" + filename, returnedBytes);

}


// Does a search and replace in the content (body) of a MS Word DOCX file using only the DocumentFormat.OpenXml.Packaging namespace.
// Reference: http://justgeeks.blogspot.co.uk/2016/03/how-to-do-search-and-replace-in.html
public static byte[] SearchAndReplace(string filename, Dictionary<string, string> oldNewValues)
{
    // make a copy of the Word document and put it in memory.
    // The code below operates on this in memory copy, not the file itself.
    // When the OpenXml SDK Auto saves the changes (that is why we don't call save explicitly)
    // the in memory copy is updated, not the original file.
    byte[] byteArray = File.ReadAllBytes(filename);
    using (MemoryStream copyOfWordFile = new MemoryStream())
    {
        copyOfWordFile.Write(byteArray, 0, (int)byteArray.Length);

        // Open the Word document for editing
        using (WordprocessingDocument wordDoc = WordprocessingDocument.Open(copyOfWordFile, true))
        {

            // Get the Main Document Part. It is really just XML.
            // NOTE: There are other parts in the Word document that we are not changing
            string bodyAsXml = null;
            using (StreamReader sr = new StreamReader(wordDoc.MainDocumentPart.GetStream()))
            {
                bodyAsXml = sr.ReadToEnd();
            }

            foreach (var keyValue in oldNewValues)
            {
                string oldValueRegex = keyValue.Key;
                string newValue = keyValue.Value;

                // Do the search and replace. Here we are implementing the logic using REGEX replace.
                Regex regexText = new Regex(oldValueRegex);
                bodyAsXml = regexText.Replace(bodyAsXml, newValue);
            }

            // After making the changes to the string we need to write the updated XML string back to
            // the Word doc (remember it is in memory, not the original file itself)
            using (StreamWriter sw = new StreamWriter(wordDoc.MainDocumentPart.GetStream(FileMode.Create)))
            {
                sw.Write(bodyAsXml);
            }
        }

        // Convert the in memory stream to a byte array (binary data)
        // NOTE: These bytes can be written to a physical file or streamed back to the browser for download, etc.
        byte[] bytesOfEntireWordFile = copyOfWordFile.ToArray();

        return bytesOfEntireWordFile;
    }
}



After calling the SearchAndReplace method you have the bytes that make up the MS Word file. It is up to you what you want to do with it. You can save it a file or stream it to the browser when a user clicks a link to download a file.

To write the file to another file (leaving the original unchanged), use the following line:

File.WriteAllBytes(newFilename, returnedBytes);


To stream the bytes back to a browser via a Action method in a ASP.NET MVC controller, use the following:

return File(returnedBytes, "application/msword", "Filename to download as here.docx");


NOTE: Original code inspired by this MSDN example and additional a post or other page I can't remember (sorry).

Friday, March 4, 2016

Modelling project gives warnings when Unity is used.

The issue as reported here and copied / summarized below:

I am using Visual Studio Enterprise 2015 and tried to create a layer diagram in order to generate and validate dependencies. But this fails because VS is throwing warnings while building the modeling project:
CurrentVersion.targets(1819,5): warning MSB3268: The primary reference "...\ClassLibrary4\bin\Debug\ClassLibrary4.dll" could not be resolved because it has an indirect dependency on the framework assembly "System.Runtime, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" which could not be resolved in the currently targeted framework. ".NETFramework,Version=v4.0". To resolve this problem, either remove the reference "...\ClassLibrary4\bin\Debug\ClassLibrary4.dll" or retarget your application to a framework version which contains "System.Runtime, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a".
I figured out that if you remove Unity the warnings are gone and dependencies are shown as expected.
What is the reason for this behavior and is there any workaround?
I tried the Unity prerelease package and also another targeting frameworks. No effect at all. The issue is reproducable with a new project after adding a modelling project and using unity in one referenced projects.

The solution that worked for me as well:

The problem that VS2015 was compiling the modelling project using the wrong target framework (4.0):
Task Parameter:TargetFrameworkDirectories=C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework.NETFramework\v4.0
There is no TargetFrameworkVersion in the project file of the modelling project (*.modelproj). But after adding it to the first property group it is compiling and validating as expected. Without any warnings.
Solution:
  1. Unload the modelling project
  2. Right Click -> Open *.modelproj
  3. Add the following line in between the first PropertyGroup open / close tags.
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
NOTE: Replace v4.5 with your target framework (the target framework for your application)

After your done, your project file will start with something like this:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
    <SchemaVersion>2.0</SchemaVersion>
    <ProjectGuid>{f60f6f86-e1d5-4d33-b8ed-0bf2172780cf}</ProjectGuid>
    <ArchitectureToolsVersion>2.0.0.0</ArchitectureToolsVersion>
    <Name>ModelingProject1</Name>
    <RootNamespace>ModelingProject1</RootNamespace>
    <SccProjectName>SAK</SccProjectName>
    <SccProvider>SAK</SccProvider>
    <SccAuxPath>SAK</SccAuxPath>
    <SccLocalPath>SAK</SccLocalPath>
    <ValidateArchitecture>true</ValidateArchitecture>
    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
  </PropertyGroup>
...




Is a Repository really a good design pattern

Here are some interesting articles on the subject.

Is the Repository pattern useful with Entity Framework - lists several posts on why the repository pattern is not really that useful and doesn't add much value.

Favor query objects over repositories - I really like this idea. It adheres to a SRP, DRY, and is testable (if you mock the entity framework though it is still not easy).