Monthly Archives: September 2017

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>