msbuild-title.png

Sometimes we need to include extra steps in the build process on a single machine involved in a project. Often this is based not on an environment specification itself, but rather on developers' personal preferences on how to approach their work. Small teams working on small projects have the highest probability of facing such problems since enforcing strict development standards for them might be an overkill.

The $(ComputerName) environmental variable in a csproj file comes to the rescue here:

<Target Name="PreBuild" AfterTargets="PreBuildEvent" Condition="Exists('$(ComputerName).prebuild.bat')">
    <Exec Command="$(ComputerName).prebuild.bat" />
</Target>

Include this target in your project's csproj file to run any script from YOUR-PC-NAME.prebuild.bat file in the root folder of the project. 

Notice a Condition on the target: a script is to be called only in case a file with your machine's name exists in the root folder. That means that any team member can opt-out of this step without any additional settings on their side.

You can also put *.prebuild.bat line in your .gitignore file, because nobody likes your personal stuff in the source code you share with a team.

Something that you may face here is a blocked MSBuild if you run a task that takes significant time to finish while not being required for the rest of the build process or a task that does not exit at all without user interaction (e.x., your ng serve or webpack --watch process).

This StackOverflow answer suggests a very useful ExecAsync implementation:

<!--Launch a Process in Parallel-->
<UsingTask TaskName="ExecAsync" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
    <ParameterGroup>
        <!--The file path is the full path to the executable file to run-->
        <FilePath ParameterType="System.String" Required="true" />
        <!--The arguments should contain all the command line arguments that need to be sent to the application-->
        <Arguments ParameterType="System.String" Required="true" />
    </ParameterGroup>
    <Task>
        <Code Type="Fragment" Language="cs">
            <![CDATA[
                System.Diagnostics.ProcessStartInfo processStartInfo = new System.Diagnostics.ProcessStartInfo(FilePath, Arguments);
                processStartInfo.UseShellExecute = false;
                System.Diagnostics.Process.Start(processStartInfo);
            ]]>
        </Code>
    </Task>
</UsingTask>

Now you can run your script with the following target:

<Target Name="PreBuild" AfterTargets="PreBuildEvent" Condition="Exists('$(ComputerName).prebuild.bat')">
    <ExecAsync FilePath="cmd.exe" Arguments="/c $(ComputerName).prebuild.bat" />
</Target>

which will give you the same result without blocking the rest of the build process.

Bonus: what's going on in TARAS-LAPTOP.prebuild.bat

tasklist /nh /fi "imagename eq code.exe" | find /i "code.exe" > nul || code client

When I start my Debug build, an instance of VS Code is launched in the ./client folder if there are none running already. Meanwhile, VS Code is configured to build Typescript files and watch for changes in them via yukidoi.blade-runner extension.

Bonus 2: just executing a Target that does not involve any scripts on a specific machine can be done with a file used as a flag:

<Target Name="CustomAction" AfterTargets="AnotherAction" Condition="Exists('$(ComputerName).msbuild.flag')">
... </Target>

Now this Target is executed only if a file with a name YOUR-PC-NAME.msbuild.flag is present.