Tag Archives: msbuild

Taming ClickOnce Deployments

Build your solution’s projects into a common directory:

<PropertyGroup>
  <BuildDir>$(SolutionDir)Build\</BuildDir>
  <Rebuilding>false</Rebuilding>
</PropertyGroup>
<PropertyGroup Condition="'$(OutputType)' == 'Exe' Or '$(OutputType)' == 'WinExe'">
  <StartAction>Program</StartAction>
  <StartWorkingDirectory>$(BuildDir)</StartWorkingDirectory>
  <StartProgram>$(BuildDir)$(TargetFileName)</StartProgram>
</PropertyGroup>
<Target Name="FlagRebuild" BeforeTargets="BeforeRebuild">
  <PropertyGroup>
    <Rebuilding>true</Rebuilding>
  </PropertyGroup>
</Target>
<Target Name="CopyBuildFiles" AfterTargets="Build">
  <ItemGroup>
    <BuildFile Include="$(TargetDir)**\*" />
  </ItemGroup>
  <Copy SourceFiles="@(BuildFile)" DestinationFolder="$(BuildDir)%(RecursiveDir)" SkipUnchangedFiles="true" />
</Target>
<Target Name="CleanBuildDir" AfterTargets="Clean" Condition="'$(Rebuilding)' == 'false'">
  <RemoveDir Directories="$(BuildDir)" />
</Target>

Create a separate launcher project that does not get built into the common directory. The launcher simply invokes the main executable, assuming it is in the same directory as the launcher executable:

using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Windows.Forms;

namespace Project
{
    public class Program
    {
        [STAThread]
        internal static void Main(string[] args)
        {
            try
            {
                DirectoryInfo directory = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory;
                string executable = Path.Combine(directory.FullName, "Project.Presentation.exe");
                Process.Start(new ProcessStartInfo
                {
                    UseShellExecute = false,
                    WorkingDirectory = directory.FullName,
                    FileName = executable
                });
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
        }
    }
}

Include the other projects’ build products (i.e., the contents of the common directory) as content files in the launcher project:

<PropertyGroup>
  <BuildDir>$(SolutionDir)Build\</BuildDir>
</PropertyGroup>
<ItemGroup>
  <Content Include="$(BuildDir)**" Exclude="$(BuildDir)**\*.pdb">
    <Link>%(RecursiveDir)%(FileName)%(Extension)</Link>
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  </Content>
</ItemGroup>
<Target Name="CleanPublishDir" AfterTargets="Clean">
  <RemoveDir Directories="publish" />
</Target>

Configuration Transforms in Non-Web Projects

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v14.0\Web\Microsoft.Web.Publishing.Tasks.dll" />
  <Target Name="TransformAppConfig" AfterTargets="Build" Condition="Exists('App.config') AND Exists('App.$(Configuration).config')">
    <TransformXml Source="App.config" Transform="App.$(Configuration).config" Destination="$(OutDir)$(TargetFileName).config" />
  </Target>
</Project>

MSBuild Task: FindFilesInList

using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using System.Collections.Generic;
using System.IO;
using System.Linq;

public class FindFilesInList : Task
{
    [Required]
    public ITaskItem[] Items { get; set; }

    [Required]
    public ITaskItem[] ItemsToFind { get; set; }

    [Output]
    public ITaskItem[] FoundItems { get; set; }

    public override bool Execute()
    {
        ICollection<string> pathsToFind = ItemsToFind.Select(taskItem => new FileInfo(taskItem.ItemSpec).FullName).ToList();
        ICollection<ITaskItem> foundItemsList = new List<ITaskItem>();
        foreach (ITaskItem item in Items)
        {
            FileInfo file = new FileInfo(item.ItemSpec);
            if (pathsToFind.Contains(file.FullName))
            {
                foundItemsList.Add(item);
            }
        }
        FoundItems = foundItemsList.ToArray();
        return true;
    }
}