Skip to content

Mass Deployment of a TwinCAT HMI project

The information in this post can be used with:

In case you like to update several remote machines with the same HMI project just implement some utilities to support this scenario. Everything you need is on-board. The source of any example in this post is accessible on GitHub in one of my repositories: “twincathmi / UseCases / MassDeployment”.

The solution “TcHmiProject1” contains two projects: (a) a simple HMI project (version 1.10) and (b) a tool to deploy the HMI project to several remote machines.

As stated in a previous post, the MsBuild task “Publish” calls the “Clean” and “Build” first. This will result in a waste of execution time, because during a mass deployment we do not really need several builds for the same project, i.e. a deployment to five remote server just need a single build of the same HMI project.

To reduce the number of “Clean” and “Build” we create a new MsBuild-target, named “PublishWithoutBuild”, see Beckhoff.TwinCAT.HMI.MassDeployment.targets. This MsBuild-target file should be imported into our HMI project. “Import” is the key MsBuild-feature for this.

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Clean;Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  [..]
  <PropertyGroup>
   <TwinCATHmi_Tasks>$(TcHmiDirectory)\MSBuild\Beckhoff.TwinCAT.HMI.tasks</TwinCATHmi_Tasks>
   <TwinCATHmi_Targets>$(TcHmiDirectory)\MSBuild\Beckhoff.TwinCAT.HMI.targets</TwinCATHmi_Targets>
   <TwinCATHmiMassDeployment_Targets>..\..\Beckhoff.TwinCAT.HMI.MassDeployment.targets</TwinCATHmiMassDeployment_Targets>
  </PropertyGroup>
  <Import Project="$(TwinCATHmi_Tasks)" />
  <Import Project="$(TwinCATHmi_Targets)" />
  <Import Project="$(TwinCATHmiMassDeployment_Targets)" />
  [..]

The target “PublishWithoutBuild” is reduced to a minimum of tasks:

<Target Name="PublishWithoutBuild">
  <PropertyGroup>
    <OutputPath Condition="'$(OutputPath)' == ''">bin\</OutputPath>
  </PropertyGroup>

  <TcHmiMSBuild.Publish.TcHmiPublish TaskAction="PublishFiles" 
    ProjectDirectory="$(TcHmi_ProjectDirectory)" 
    ProfilesFilename="$(TcHmi_ProfilesFilename)" 
    ProfileName="$(TcHmi_ProfileName)" 
    OutputPath="$(OutputPath)" SuppressTaskMessages="False">
    <Output TaskParameter="Result" PropertyName="TaskResult" />
  </TcHmiMSBuild.Publish.TcHmiPublish>

  <TcHmiMSBuild.Publish.TcHmiPublish TaskAction="PublishExtensions" 
    ProfilesFilename="$(TcHmi_ProfilesFilename)" 
    ProfileName="$(TcHmi_ProfileName)" 
    LocalServer="$(TcHmi_LocalServer)" 
    ExtensionInformation="@(TcHmiReference)">
    <Output TaskParameter="Result" PropertyName="TaskResult"/>
  </TcHmiMSBuild.Publish.TcHmiPublish>

  <TcHmiMSBuild.Publish.TcHmiPublish TaskAction="PublishConfiguration" 
    ProjectDirectory="$(TcHmi_ProjectDirectory)" 
    ProfilesFilename="$(TcHmi_ProfilesFilename)" 
    ProfileName="$(TcHmi_ProfileName)" 
    LocalServer="$(TcHmi_LocalServer)" 
    ProjectFiles="@(Folder);@(Content)" 
    ExtensionInformation="@(TcHmiReference)">
    <Output TaskParameter="Result" PropertyName="TaskResult"/>
  </TcHmiMSBuild.Publish.TcHmiPublish>

  <OnError ExecuteTargets="PublishError"/>
</Target>

Publish to several remote TwinCAT HMI server programmatically

As stated above we will use our new MsBuild-target “PublishWithoutBuild”. In this post we will call the relevant MsBuild-steps programmatically with C#. The full implementation is provided in MultipleRemoteDeployment/Program.cs.

Extend the dictionary TargetRemoteServer with your remote server addresses:

public static Dictionary<string, int> TargetRemoteServer = new Dictionary<string, int> {
            { "127.0.0.1", 1010 },
            { "172.17.62.179", 1010 }
        };

At the beginning we will build the HMI-project once. During publish the result of this build is used. The engineering TwinCAT HMI server for the related HMI-project must run, because the local server configuration is queried and transfered to the remote servers.

The following code shows how a build with MsBuild can be triggered programmatically (here the “Build”-target is executed):

var propsBuild = MsBuildInfo.Instance();
propsBuild.ProjectDirectory = Path.GetDirectoryName(PathToHmiProject);
var propsDict = propsBuild.GetProperties();
var hmiBuildRequest = new BuildRequestData(PathToHmiProject, propsDict, null, 
   new string[] { "Build"   // the HMI-project is built
 }, null);
            
var pc = new ProjectCollection();
var bp = new BuildParameters(pc) {
      Loggers = new[] { new TargetLogger() }
};

var hmiBuildResult = BuildManager.DefaultBuildManager.Build(bp, hmiBuildRequest);

Console.WriteLine("Result: {0}", hmiBuildResult.OverallResult);

Any publish/deployment to a remote machine requires an individual publish profile, i.e. because during publish the target/remote ip adress and port can vary. Here, the key-value-entries of TargetRemoteServer are used. During the iteration the publish profile is modified with the ip/port of the current iteration and “PublishWithoutBuild” is called.

foreach (var it in TargetRemoteServer) {
  jsonResponse[0].tcHmiServerHost = it.Key;
  jsonResponse[0].tcHmiServerPort = it.Value;

  #region msbuild magic for publish

  var props = MsBuildInfo.Instance();
  props.ServerAddress = it.Key;
  props.ServerPort = it.Value;
  props.ProfileName = "BasePublishProfile";
  props.ProfilePath = PathToHmiPublishProfile;
  props.LocalServer = "ws://127.0.0.1:3000";
  props.ProjectDirectory = Path.GetDirectoryName(PathToHmiProject);
  props.OutputPath = Path.Combine(props.ProjectDirectory, @"bin\");

  var buildRequest = new BuildRequestData(PathToHmiProject, props.GetProperties(), 
    null, new string[] { "PublishWithoutBuild" }, null);
  var buildResult = BuildManager.DefaultBuildManager.Build(bp, buildRequest);

  Console.WriteLine("Result: {0}", buildResult.OverallResult);

  #endregion
}

The execution with LoggerVerbosity.Minimal looks like this:

Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.