Pipeline to fix the odd promotion of AS2To.
See StackOverflow – Why field is promoted as AS2ToInternal instead of AS2To

This C# pipeline code checks for that issues and fixes it by promoting the proper field. I never found out why it was happening and why it needed to be fixed.

using System;
using System.Linq;
using System.Xml;
using System.Text;
using System.Collections;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.IO;
using System.Resources;
using System.Reflection;
using System.Drawing;
using Microsoft.BizTalk.Message.Interop;
using Microsoft.BizTalk.Component.Interop;
using Microsoft.BizTalk.Component.Utilities;
using Microsoft.XLANGs.RuntimeTypes;
using Microsoft.XLANGs.BaseTypes;

namespace Microsoft.Samples.BizTalk.Pipelines.CustomComponent
{
	/// <summary>
	/// Pipeline component which can't be placed into any receive or send
	/// pipeline stage and do a property promotion / distinguished fields 
	/// writing based on arbitrary XPath.
	/// </summary>
	[ComponentCategory(CategoryTypes.CATID_PipelineComponent)]
	[ComponentCategory(CategoryTypes.CATID_Any)]
    [System.Runtime.InteropServices.Guid("EC9794D0-AE8F-42B9-A7E7-02A876998158")]

    public class SetAS2ToPromotedField : 
		BaseCustomTypeDescriptor,
		IBaseComponent, 
		Microsoft.BizTalk.Component.Interop.IComponent, 
		IPersistPropertyBag,
		IComponentUI
	{

        private string _Encoding = "ASCII"; 


        public SetAS2ToPromotedField()
            : base(resourceManager)
		{
		}

		#region Design-time property(ies)

        
        #endregion

		#region IBaseComponent Members

		/// <summary>
		/// Gets a component description.
		/// </summary>
		[Browsable(false)]
		public string Description
		{
            get 
            {
                return "SetAS2To from AS2ToInternal"; 
            }
		}

		/// <summary>
		/// Gets a component name.
		/// </summary>
		[Browsable(false)]
		public string Name
		{
            get 
            {
				return "SetAS2To";
            }
		}

		/// <summary>
		/// Gets a component version.
		/// </summary>
		[Browsable(false)]
		public string Version
		{
			get
			{
				return "1.0";
			}
		}

		#endregion

		#region IComponentUI Members

		/// <summary>
		/// Validate component.
		/// </summary>
		/// <param name="projectSystem">Project system</param>
		/// <returns>Enumerator of error message collection</returns>
		public IEnumerator Validate(object projectSystem)
		{
			// Don't do any validation
			return null;
		}

		/// <summary>
		/// Gets a component icon.
		/// </summary>
		[Browsable(false)]
		public System.IntPtr Icon
		{
			get
			{
				//return ((Bitmap) resourceManager.GetObject("CompIcon")).GetHicon();
                return new System.IntPtr(); 
			}
		}

		#endregion

		#region IComponent Members

		/// <summary>
		/// Executes a pipeline component.
		/// </summary>
		/// <param name="pContext">Pipeline context</param>
		/// <param name="pInMsg">Input message</param>
		/// <returns>Outgoing message</returns>
		public IBaseMessage Execute(IPipelineContext pContext, IBaseMessage pInMsg)
		{
			try
			{
				return ExecuteInternal(pContext, pInMsg);
			}
			catch(Exception e)
			{
				// Put component name as a source information in this exception,
				// so the event log in message could reflect this.
				e.Source = Name;
				throw e;
			}
		}

		#endregion

		#region IPersistPropertyBag Members

		/// <summary>
		/// Initialize component.
		/// </summary>
		public void InitNew()
		{
			// Do nothing
		}

		/// <summary>
		/// Get component class ID.
		/// </summary>
		/// <param name="classID"></param>
		public void GetClassID(out Guid classID)
		{
			classID = new Guid("1E7EC223-338E-4D4C-B275-FE9AD90ECE6C");
    }

		/// <summary>
		/// Load component properties from a property bag.
		/// </summary>
		/// <param name="propertyBag">Property bag</param>
		/// <param name="errorLog">Error log level</param>
		public void Load(IPropertyBag propertyBag, int errorLog)
		{
			if ( null == propertyBag )
				throw new ArgumentNullException("propertyBag");

            //string val = (string)ReadPropertyBag(pb, "XPath1");
            //if (val != null) _XPath1 = val;
            string val; 

            //val = (string)ReadPropertyBag(propertyBag, "FilenamePattern");
            //if (val != null) _FilenamePattern = val;
        }

		/// <summary>
		/// Save component properties to a property bag.
		/// </summary>
		/// <param name="propertyBag">Property bag</param>
		/// <param name="clearDirty">Clear dirty flag</param>
		/// <param name="saveAllProperties">Save all properties flag</param>
		public void Save(IPropertyBag propertyBag, bool clearDirty, bool saveAllProperties)
		{
			if ( null == propertyBag )
				throw new ArgumentNullException("propertyBag");

            //propertyBag.Write("FilenamePattern", _FilenamePattern);


		}

        #endregion

        #region Private Members

        /// <summary>
        /// Executes the logic for this component.
        /// </summary>
        /// <param name="pContext">Pipeline context</param>
        /// <param name="pInMsg">Input message</param>
        /// <returns>Outgoing message</returns>
        private IBaseMessage ExecuteInternal(IPipelineContext pContext, IBaseMessage pInMsg)
        {
            // Check arguments
            if (null == pContext)
                throw new ArgumentNullException("pContext");

            if (null == pInMsg)
                throw new ArgumentNullException("pInMsg");

            if (null == pInMsg.BodyPart)
                throw new ArgumentNullException("pInMsg.BodyPart");

            if (null == pInMsg.BodyPart.GetOriginalDataStream())
                throw new ArgumentNullException("pInMsg.BodyPart.GetOriginalDataStream()");


            //
            // Create a seekable read-only stream over the input message body part stream.
            //

            // Create a virtual stream, using GetOriginalDataStream() method on the IBaseMessagePart because
            // this call doesn't clone stream (instead of IBaseMessagePart.Data property).
            SeekableReadOnlyStream stream = new SeekableReadOnlyStream(pInMsg.BodyPart.GetOriginalDataStream());

            //
            // Create a new outgoing message, copy all required stuff.
            //

            // Create a new output message
            IBaseMessage outMessage = pContext.GetMessageFactory().CreateMessage();

            // Copy message context by reference
            outMessage.Context = pInMsg.Context;

            // Create new message body part
            IBaseMessagePart newBodyPart = pContext.GetMessageFactory().CreateMessagePart();

            // Copy body part properties by references.
            newBodyPart.PartProperties = pInMsg.BodyPart.PartProperties;

            // Neal added so we can change data in the message itself with replace commands
            StreamReader reader = new StreamReader(stream);
            string text = reader.ReadToEnd();

            byte[] byteArray;
            _Encoding = "ASCII";
            if (_Encoding == "UNICODE")
            {
                byteArray = System.Text.Encoding.Unicode.GetBytes(text);
            }
            else
            {
                byteArray = System.Text.Encoding.ASCII.GetBytes(text);
            }
            MemoryStream memStream = new MemoryStream(byteArray);



            string strAS2Namespace = "http://schemas.microsoft.com/BizTalk/2006/as2-properties";


            object objAS2To = pInMsg.Context.Read("AS2To", strAS2Namespace);
            string strAS2To = objAS2To != null ? objAS2To.ToString() : "";

            object objAS2From = pInMsg.Context.Read("AS2From", strAS2Namespace);
            string strAS2From = objAS2From != null ? objAS2From.ToString() : "";

            // Absolutely no idea why were are seeing the AS2To field promoted as AS2ToInternal. 
            // https://stackoverflow.com/questions/61464238/biztalk-as2-why-field-is-promoted-as-as2tointernal-instead-of-as2to 
            object objAS2ToInternal = pInMsg.Context.Read("AS2ToInternal", strAS2Namespace);
            string strAS2ToInternal = objAS2ToInternal != null ? objAS2ToInternal.ToString() : "";

            object objPreservedFileName = pInMsg.Context.Read("PreservedFileName", strAS2Namespace);
            string strPreservedFileName = objPreservedFileName != null ? objPreservedFileName.ToString() : "";

            string strHttpNamespace = "http://schemas.microsoft.com/BizTalk/2003/http-properties";
            object objHttpHeaders = pInMsg.Context.Read("InboundHttpHeaders", strHttpNamespace);
            string strHttpHeaders = objHttpHeaders != null ? objHttpHeaders.ToString() : "";

            // could not get debugger to work, so had to try Trace 
            string trcGUID = System.Guid.NewGuid().ToString();
            string trcData = "Filename=" + strPreservedFileName;
            string trcXML = "InboundHttpHeaders:" + strHttpHeaders;


            // If No AS2To found, then substitute the next best possible value by promoting it 
            string strNewAS2To = null;
            if (String.IsNullOrEmpty(strAS2To))
            {

                // NOTE: if these two don't work, we could also parse out values from the Httpheader 
                // (but not there, the value is AS2-To 
                if (!String.IsNullOrEmpty(strAS2ToInternal))
                {
                    strNewAS2To = strAS2ToInternal;
                }
                else
                {
                    throw new System.ApplicationException("Could not find any possible value for AS2To. " + 
                        " InboundHttpHeaders=" + strHttpHeaders 
                        );
                }

                // based on above logic, promote a new value to AS2To if needed 
                //pInMsg.Context.Write("AS2To", strAS2Namespace, strNewAS2To);
                pInMsg.Context.Promote("AS2To", strAS2Namespace, strNewAS2To);

            }


            // Set virtual stream as a data stream for the new message body part
            //newBodyPart.Data = stream;
            newBodyPart.Data = memStream;
            pContext.ResourceTracker.AddResource(memStream);

			// Copy message parts
			CopyMessageParts(pInMsg, outMessage, newBodyPart);

            //
            // Return outgoing message.
            //

            return outMessage;
		}



        public static long GetRandomNumberInRange(double minNumber, double maxNumber)
        {
            double dblRandomNumber = new Random().NextDouble() * (maxNumber - minNumber) + minNumber;
            long longRandomNumber = System.Convert.ToInt64(Math.Round(dblRandomNumber, 0));
            return longRandomNumber;
        }


		/// <summary>
		/// Copy message parts from source to destination message.
		/// </summary>
		/// <param name="sourceMessage">Source message</param>
		/// <param name="destinationMessage">Destination message</param>
		/// <param name="newBodyPart">New message body part</param>
		private void CopyMessageParts(IBaseMessage sourceMessage, IBaseMessage destinationMessage, IBaseMessagePart newBodyPart)
		{
			string bodyPartName = sourceMessage.BodyPartName;
			for (int c = 0; c < sourceMessage.PartCount; ++c)
			{
				string partName = null;
				IBaseMessagePart messagePart = sourceMessage.GetPartByIndex(c, out partName);
				if (partName != bodyPartName)
				{
					destinationMessage.AddPart(partName, messagePart, false);
				}
				else
				{
					destinationMessage.AddPart(bodyPartName, newBodyPart, true);
				}
			}
		}

		/// <summary>
		/// Gets a message type from the XML stream.
		/// </summary>
		/// <param name="stream">Seekable stream</param>
		/// <returns>Message type</returns>
		/// <exception cref="InvalidOperationException">If message type can't be determined</exception>
		private string GetMessageType(Stream stream)
		{
			// Fail if message stream is not seekable
			if (!stream.CanSeek)
				throw new InvalidOperationException("An attempt to get a message type for non-seekable stream");

			// Save the current stream position
			long position = stream.Position;
			try
			{
				// Create XmlTextReader over the message stream
				XmlTextReader reader = new XmlTextReader(stream);
				while (reader.Read())
				{
					// Check if current node in XmlTextReader is element and it's global
					if (XmlNodeType.Element == reader.NodeType && 0 == reader.Depth)
					{
						// Found a global element, now build message type as <namespaceURI>#<localName> 
						// if namespaceURI is not empty and <localName> if namespaceURI is empty.
						if (reader.NamespaceURI != null && reader.NamespaceURI.Length > 0)
						{
							return reader.NamespaceURI + '#' + reader.LocalName;
						}
						else
						{
							return reader.LocalName;
						}
					}
				}

				// Not found, fail
				throw new InvalidOperationException("Message type can't be determined");
			}
			finally
			{
				// Restore the stream position
				stream.Position = position;
			}
		}

		/// <summary>
		/// Read property bag in order to eat the argument exception which is thrown
		/// when properties are not populated.
		/// </summary>
		/// <param name="propertyBag">Property bag</param>
		/// <param name="propertyName">Property name</param>
		/// <returns>Property value</returns>
		private object ReadPropertyBag(IPropertyBag propertyBag, string propertyName)
		{
			object value;
			try
			{
				propertyBag.Read(propertyName, out value, 0);
				return value;
			}
			catch (ArgumentException)
			{
				// IPropertyBag.Read throws an ArgumentException if there are no properties in there.
				// Just return null in this case.
				return null;
			}
		}

		private SchemaList documentSchemaList = new SchemaList();

		private static PromotingMap promotingMap = new PromotingMap();
		private static ResourceManager resourceManager = new ResourceManager("Microsoft.Samples.BizTalk.Pipelines.CustomComponent.ArbitraryXPathPropertyHandler", Assembly.GetExecutingAssembly());
		private static PropertyBase messageTypeWrapper = new BTS.MessageType();

		//private const string DocumentSpecNamesPropertyName = "DocumentSpecNames";
		//private const string DocumentSpecNamespacesPropertyName = "DocumentSpecNamespaces";
		//private const char   DocumentSpecNamesSeparator = '|';
		
		#endregion
	}
}