diff --git a/src/wix/WixToolset.Converters/WixConverter.cs b/src/wix/WixToolset.Converters/WixConverter.cs
index 2d75c5e5f..3fea44986 100644
--- a/src/wix/WixToolset.Converters/WixConverter.cs
+++ b/src/wix/WixToolset.Converters/WixConverter.cs
@@ -1033,7 +1033,7 @@ private void ConvertControlElement(XElement element)
             using (var lab = new ConversionLab(element))
             {
                 var xConditions = element.Elements(ConditionElementName).ToList();
-                var context = new InnerTextContext();
+                var collector = new InnerContentCollector();
                 var conditions = new List<KeyValuePair<string, string>>();
 
                 foreach (var xCondition in xConditions)
@@ -1041,10 +1041,10 @@ private void ConvertControlElement(XElement element)
                     var action = UppercaseFirstChar(xCondition.Attribute("Action")?.Value);
                    
                     if (!String.IsNullOrEmpty(action) &&
-                        TryGetInnerText(xCondition, context) &&
+                        collector.CollectInnerTextAndCommentsForAttributeValue(xCondition, out string value) &&
                         this.OnInformation(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the '{1}Condition' attribute instead.", xCondition.Name.LocalName, action))
                     {
-                        conditions.Add(new KeyValuePair<string, string>(action, context.Value));
+                        conditions.Add(new KeyValuePair<string, string>(action, value));
                     }
                 }
 
@@ -1063,7 +1063,7 @@ private void ConvertControlElement(XElement element)
                 }
 
                 lab.RemoveOrphanTextNodes();
-                lab.AddCommentsAsSiblings(context.Comments);
+                lab.AddCommentsAsSiblings(collector.Comments);
             }
         }
 
@@ -1081,17 +1081,17 @@ private void ConvertComponentElement(XElement element)
             var xCondition = element.Element(ConditionElementName);
             if (xCondition != null)
             {
-                var context = new InnerTextContext();
+                var collector = new InnerContentCollector();
 
-                if (TryGetInnerText(xCondition, context) &&
+                if (collector.CollectInnerTextAndCommentsForAttributeValue(xCondition, out string value) &&
                     this.OnInformation(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the 'Condition' attribute instead.", xCondition.Name.LocalName))
                 {
                     using (var lab = new ConversionLab(element))
                     {
                         xCondition.Remove();
-                        element.Add(new XAttribute("Condition", context.Value));
+                        element.Add(new XAttribute("Condition", value));
                         lab.RemoveOrphanTextNodes();
-                        lab.AddCommentsAsSiblings(context.Comments);
+                        lab.AddCommentsAsSiblings(collector.Comments);
                     }
                 }
             }
@@ -1194,18 +1194,18 @@ private void ConvertFeatureElement(XElement element)
             if (xCondition != null)
             {
                 var level = xCondition.Attribute("Level")?.Value;
-                var context = new InnerTextContext();
+                var collector = new InnerContentCollector();
 
                 if (!String.IsNullOrEmpty(level) &&
-                    TryGetInnerText(xCondition, context) &&
+                    collector.CollectInnerTextAndCommentsForAttributeValue(xCondition, out string value) &&
                     this.OnInformation(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the 'Level' element instead.", xCondition.Name.LocalName))
                 {
                     using (var lab = new ConversionLab(xCondition))
                     {
                         lab.ReplaceTargetElement(new XElement(LevelElementName,
                                                               new XAttribute("Value", level),
-                                                              new XAttribute("Condition", context.Value)));
-                        lab.AddCommentsAsSiblings(context.Comments);
+                                                              new XAttribute("Condition", value)));
+                        lab.AddCommentsAsSiblings(collector.Comments);
                     }
                 }
             }
@@ -1240,23 +1240,23 @@ private void ConvertFileElement(XElement element)
         private void ConvertLaunchConditionElement(XElement element)
         {
             var message = element.Attribute("Message")?.Value;
-            var context = new InnerTextContext() { ReportWhitespace = true };
+            var collector = new InnerContentCollector();
 
             if (!String.IsNullOrEmpty(message) &&
-                TryGetInnerText(element, context) &&
+                collector.CollectInnerTextWithTrailingWhitespaceAndCommentsForAttributeValue(element, out string value) &&
                 this.OnInformation(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the 'Launch' element instead.", element.Name.LocalName))
             {
-                if (String.IsNullOrWhiteSpace(context.Value))
+                if (String.IsNullOrWhiteSpace(value))
                 {
-                    context.Value = String.Empty;
+                    value = String.Empty;
                 }
 
                 using (var lab = new ConversionLab(element))
                 {
                     lab.ReplaceTargetElement(new XElement(LaunchElementName,
-                                                          new XAttribute("Condition", context.Value),
+                                                          new XAttribute("Condition", value),
                                                           new XAttribute("Message", message)));
-                    lab.AddCommentsAsSiblings(context.Comments);
+                    lab.AddCommentsAsSiblings(collector.Comments);
                 }
             }
         }
@@ -1301,8 +1301,8 @@ private void ConvertPermissionExElement(XElement element)
             var xCondition = element.Element(ConditionElementName);
             if (xCondition != null)
             {
-                var context = new InnerTextContext();
-                if (TryGetInnerText(xCondition, context) &&
+                var collector = new InnerContentCollector();
+                if (collector.CollectInnerTextAndCommentsForAttributeValue(xCondition, out string value) &&
                     this.OnInformation(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the 'Condition' attribute instead.", xCondition.Name.LocalName))
                 {
                     using (var lab = new ConversionLab(xCondition))
@@ -1311,9 +1311,9 @@ private void ConvertPermissionExElement(XElement element)
                     }
                     using (var lab = new ConversionLab(element))
                     {
-                        element.Add(new XAttribute("Condition", context.Value));
+                        element.Add(new XAttribute("Condition", value));
                         lab.RemoveOrphanTextNodes();
-                        lab.AddCommentsAsSiblings(context.Comments);
+                        lab.AddCommentsAsSiblings(collector.Comments);
                     }
                 }
             }
@@ -1681,35 +1681,35 @@ private void ConvertCustomActionRefElement(XElement element)
 
         private void ConvertPublishElement(XElement element)
         {
-            var context = new InnerTextContext();
+            var collector = new InnerContentCollector();
 
-            if (TryGetInnerText(element, context) &&
+            if (collector.CollectInnerTextAndCommentsForAttributeValue(element, out string value) &&
                 this.OnInformation(ConverterTestType.InnerTextDeprecated, element, "Using {0} element text is deprecated. Use the 'Condition' attribute instead.", element.Name.LocalName))
             {
                 using (var lab = new ConversionLab(element))
                 {
-                    if ("1" == context.Value)
+                    if ("1" == value)
                     {
                         this.OnInformation(ConverterTestType.PublishConditionOneUnnecessary, element, "Adding Condition='1' on {0} elements is no longer necessary. Remove the Condition attribute.", element.Name.LocalName);
                     }
                     else
                     {
-                        element.Add(new XAttribute("Condition", context.Value));
+                        element.Add(new XAttribute("Condition", value));
                     }
 
                     lab.RemoveOrphanTextNodes();
-                    lab.AddCommentsAsSiblings(context.Comments);
+                    lab.AddCommentsAsSiblings(collector.Comments);
                 }
             }
 
-            var evnt = element.Attribute("Event")?.Value;
-            var value = element.Attribute("Value")?.Value;
+            var eventName = element.Attribute("Event")?.Value;
+            var eventValue = element.Attribute("Value")?.Value;
 
-            if (evnt?.Equals("DoAction", StringComparison.OrdinalIgnoreCase) == true
-                && value?.StartsWith("WixUI", StringComparison.OrdinalIgnoreCase) == true
+            if (eventName?.Equals("DoAction", StringComparison.OrdinalIgnoreCase) == true
+                && eventValue?.StartsWith("WixUI", StringComparison.OrdinalIgnoreCase) == true
                 && this.OnInformation(ConverterTestType.CustomActionIdsIncludePlatformSuffix, element, "Custom action ids have changed in WiX v4 extensions to support platform-specific custom actions. For more information, see https://wixtoolset.org/docs/fourthree/#converting-custom-wixui-dialog-sets."))
             {
-                element.Attribute("Value").Value = value + "_$(sys.BUILDARCHSHORT)";
+                element.Attribute("Value").Value = eventValue + "_$(sys.BUILDARCHSHORT)";
             }
         }
 
@@ -1996,16 +1996,10 @@ private void ConvertCustomActionElement(XElement xCustomAction)
             }
 
             var xScript = xCustomAction.Attribute("Script");
-            var context = new InnerTextContext() { CoalesceWhitespace = false };
+            var collector = new InnerContentCollector();
 
-            if (xScript != null && TryGetInnerText(xCustomAction, context))
+            if (xScript != null && collector.CollectInnerTextWithTrailingWhitespaceAndCommentsForScriptFile(xCustomAction, out string value))
             {
-                if (null != context.Value)
-                {
-                    char[] whitespaceChars = { ' ', '\t', '\r', '\n' };
-                    context.Value = context.Value.Trim(whitespaceChars);
-                }
-
                 if (this.OnInformation(ConverterTestType.InnerTextDeprecated, xCustomAction, "Using {0} element text is deprecated. Extract the text to a file and use the 'ScriptSourceFile' attribute to reference it.", xCustomAction.Name.LocalName))
                 {
                     var scriptFolder = Path.GetDirectoryName(this.SourceFile) ?? String.Empty;
@@ -2013,12 +2007,12 @@ private void ConvertCustomActionElement(XElement xCustomAction)
                     var ext = (xScript.Value == "jscript") ? ".js" : (xScript.Value == "vbscript") ? ".vbs" : ".txt";
 
                     var scriptFile = Path.Combine(scriptFolder, id + ext);
-                    File.WriteAllText(scriptFile, context.Value);
+                    File.WriteAllText(scriptFile, value);
 
                     RemoveChildren(xCustomAction);
                     xCustomAction.Add(new XAttribute("ScriptSourceFile", scriptFile));
 
-                    if (context.Comments.Any())
+                    if (collector.Comments.Any())
                     {
                         var remainingNodes = xCustomAction.NodesAfterSelf().ToList();
                         var replacementNodes = remainingNodes.Where(e => XmlNodeType.Text != e.NodeType);
@@ -2026,7 +2020,7 @@ private void ConvertCustomActionElement(XElement xCustomAction)
                         {
                             node.Remove();
                         }
-                        foreach (var comment in context.Comments)
+                        foreach (var comment in collector.Comments)
                         {
                             xCustomAction.Add(comment);
                             xCustomAction.Add("\n");
@@ -2165,9 +2159,9 @@ private void ConvertWixLocalizationUIElement(XElement element)
 
         private void ConvertInnerTextToAttribute(XElement element, string attributeName)
         {
-            var context = new InnerTextContext();
+            var collector = new InnerContentCollector();
 
-            if (TryGetInnerText(element, context))
+            if (collector.CollectInnerTextAndCommentsForAttributeValue(element, out string value))
             {
                 // If the target attribute already exists, error if we have anything more than whitespace.
                 var attribute = element.Attribute(attributeName);
@@ -2180,8 +2174,8 @@ private void ConvertInnerTextToAttribute(XElement element, string attributeName)
                     using (var lab = new ConversionLab(element))
                     {
                         lab.RemoveOrphanTextNodes();
-                        element.Add(new XAttribute(attributeName, context.Value));
-                        lab.AddCommentsAsSiblings(context.Comments);
+                        element.Add(new XAttribute(attributeName, value));
+                        lab.AddCommentsAsSiblings(collector.Comments);
                     }
                 }
             }
@@ -2874,142 +2868,6 @@ private static void RenameElementToStandardDirectory(XElement element)
             }
         }
 
-        // This function is used to simultaneously extract the inner text of, and any comments embedded in, an XElement.
-        // The comments are returned in a list, so there can be several of them. In addition, the code is designed so that
-        // it can be called in a loop, processing one element at a time, extracting the comments cumulatively, and returning
-        // the inner text separately with each call.
-        //
-        // This function's first parameter refers to an XElement containinng inner text and comments to be extracted.
-        //
-        // Other information is passed to and from TryGetInnerText() through the properties of an InnerTextContext object,
-        // which is passed to this function as its second parameter.
-        //
-        // On entry, the Comments property of the InnerTextContext object contains a List<XNode> erpresenting the comments
-        // collected during any previous calls to this function using this InnerTextCntext object. TryGetInnerText() adds
-        // any comments it finds to this List<XNode>.
-        //
-        // The inner text is returned through the Value property of the InnerTextContext object.
-        //
-        // The function, itself, returns true if inner text is found.
-        //
-        // There are two other properties of the InnerTextContext object that control the behavior of the function: The CoalesceWhitespace
-        // property, with a default value of true, causes contiguous whitespace to be coalesced into a single space character and leading and
-        // trailing whitespace to be trimmed. When this paarameter is false, the inner text is returned with all whitespace intact.
-        //
-        // The other property of the InnerTextContext object, ReportWhitespace, has a default value of "false". When this property is
-        // false, the text found is trimmed of leading and trailing whitespace before being returned and the function only returns true
-        // if the returned text is not empty. If it is true, the value returned by the function is true, not only when non-whitespace
-        // text is returned in the Value property, but also when only whitespace is found. In this case, the text returned is a single space.
-        //
-        // There is one other important case in which the function returns true: If a CDATA node is processed, even if all the whitespace
-        // removed causes the remaining text to be empty, the function returns true. If in fact, the remaining text is empty, the text
-        // returned is a single space.
-        private static bool TryGetInnerText(XElement element, InnerTextContext context)
-        {
-            context.Value = null;
-            char[] whitespaceChars = { ' ', '\t', '\r', '\n' };
-            var nodes = element.Nodes().ToList();
-            var inWhitespace = false;
-            var cDataFound = false;
-            var whitespaceFound = false;
-            var sb = new StringBuilder();
-
-            foreach (var node in nodes)
-            {
-                if (XmlNodeType.Comment == node.NodeType)
-                {
-                    context.Comments.Add(node);
-                }
-                else if (XmlNodeType.CDATA == node.NodeType || XmlNodeType.Text == node.NodeType)
-                {
-                    var isCData = XmlNodeType.CDATA == node.NodeType;
-
-                    if (isCData)
-                    {
-                        cDataFound = true;
-                    }
-
-                    var text = (node as XText)?.Value;
-
-                    if (context.CoalesceWhitespace)
-                    {
-                        var nodeSB = new StringBuilder();
-
-                        foreach (var c in text)
-                        {
-                            char? emit = c;
-
-                            // Replace contiguous whitespace with a single space.
-                            if (' ' == c || '\r' == c || '\n' == c || '\t' == c)
-                            {
-                                if (!inWhitespace)
-                                {
-                                    inWhitespace = true;
-                                    whitespaceFound = true;
-                                    emit = ' ';
-                                }
-                                else
-                                {
-                                    emit = null;
-                                }
-                            }
-                            else
-                            {
-                                inWhitespace = false;
-                            }
-
-                            if (emit.HasValue)
-                            {
-                                nodeSB.Append(emit);
-                            }
-                        }
-
-                        text = nodeSB.ToString();
-                    }
-
-                    if (0 < text.Length)
-                    {
-                        text = text.Trim(whitespaceChars);
-
-                        if (0 == text.Length && context.ReportWhitespace)
-                        {
-                            text = " ";
-                        }
-                    }
-
-                    sb.Append(text);
-                }
-            }
-
-            var found = false;
-
-            context.Value = sb.ToString();
-
-            if (0 < context.Value.Length)
-            {
-                found = true;
-            }
-
-            context.Value = context.Value.Trim(whitespaceChars);
-
-            if (context.ReportWhitespace && whitespaceFound)
-            {
-                found = true;
-            }
-
-            if (cDataFound)
-            {
-                found = true;
-
-                if (0 == context.Value.Length)
-                {
-                    context.Value = " ";
-                }
-            }
-
-            return found;
-        }
-
         private static bool IsTextNode(XNode node, out XText text)
         {
             text = null;
@@ -3079,41 +2937,247 @@ private static bool WasImplicitlyStringTyped(string value)
             return true;
         }
 
-        // This class represents the context in which TryGetInnerText() is called.
-        //
-        // Upon return from TryGetInnerText(), the Value property represents a string comprisiing the non-comment portion of an XML element's inner text.
-        //
-        // The Comments property always returns a (possibly empty) List<XNode> representing any comments that have been accumulated from previous calls
-        // to TryGetInnerText(), using the same InnerTextContext object. Even if the intended call to TryGetInnerText is short-circuited, the Comments
-        // property still returns the intended List<XNode>.
-        //
-        // Upon entry to TryGetInnerText(), the CoalesceWhitespace and reportWhitespace properties contain values that control the behavior of TryGetInnerContext(),
-        // as explained in the introductory comments of that function.
-        private class InnerTextContext
+        // This class encapsulates methods for extraving text and comments from XElements. Multiple calls can be made to the collection
+        // methods to processs multiple XElements. The Comments property is used to extract the list of comments accumulated during the collection process.
+        private class InnerContentCollector
         {
-            internal string Value { get; set; } = null;
-            internal List<XNode> comments = null;
+            public InnerContentCollector()
+            {
+                this.Comments = new List<XNode>();
+            }
 
-            internal List<XNode> Comments
+            public List<XNode> Comments { get; private set; }
+
+            public bool CollectInnerTextAndCommentsForAttributeValue(XElement element, out string collectedText)
             {
-                get
+                char[] whitespaceChars = { ' ', '\t', '\r', '\n' };
+                var nodes = element.Nodes().ToList();
+                var inWhitespace = false;
+                var cDataFound = false;
+                var sb = new StringBuilder();
+
+                foreach (var node in nodes)
                 {
-                    if (null == this.comments)
+                    if (XmlNodeType.Comment == node.NodeType)
                     {
-                        this.comments = new List<XNode>();
+                        this.Comments.Add(node);
                     }
+                    else if (XmlNodeType.CDATA == node.NodeType || XmlNodeType.Text == node.NodeType)
+                    {
+                        var isCData = XmlNodeType.CDATA == node.NodeType;
+
+                        if (isCData)
+                        {
+                            cDataFound = true;
+                        }
+
+                        var text = node is XText xtext ? xtext.Value : String.Empty;
+                        var nodeSB = new StringBuilder();
+
+                        foreach (var c in text)
+                        {
+                            char? emit = c;
+
+                            // Replace contiguous whitespace with a single space.
+                            if (' ' == c || '\r' == c || '\n' == c || '\t' == c)
+                            {
+                                if (!inWhitespace)
+                                {
+                                    inWhitespace = true;
+                                    emit = ' ';
+                                }
+                                else
+                                {
+                                    emit = null;
+                                }
+                            }
+                            else
+                            {
+                                inWhitespace = false;
+                            }
 
-                    return this.comments;
+                            if (emit.HasValue)
+                            {
+                                nodeSB.Append(emit);
+                            }
+                        }
+
+                        text = nodeSB.ToString().Trim(whitespaceChars);
+                        sb.Append(text);
+                    }
                 }
+                var found = false;
+                collectedText = sb.ToString();
 
-                set => this.comments = value;
+                if (0 < collectedText.Length)
+                {
+                    found = true;
+                }
+
+                collectedText = collectedText.Trim(whitespaceChars);
+
+                if (cDataFound)
+                {
+                    found = true;
+
+                    if (0 == collectedText.Length)
+                    {
+                        collectedText = " ";
+                    }
+                }
+
+                return found;
             }
 
-            internal bool CoalesceWhitespace { get; set; } = true;
-            internal bool ReportWhitespace { get; set; } = false;
+            public bool CollectInnerTextWithTrailingWhitespaceAndCommentsForAttributeValue(XElement element, out string collectedText)
+            {
+                char[] whitespaceChars = { ' ', '\t', '\r', '\n' };
+                var nodes = element.Nodes().ToList();
+                var inWhitespace = false;
+                var cDataFound = false;
+                var whitespaceFound = false;
+                var sb = new StringBuilder();
+
+                foreach (var node in nodes)
+                {
+                    if (XmlNodeType.Comment == node.NodeType)
+                    {
+                        this.Comments.Add(node);
+                    }
+                    else if (XmlNodeType.CDATA == node.NodeType || XmlNodeType.Text == node.NodeType)
+                    {
+                        var isCData = XmlNodeType.CDATA == node.NodeType;
+
+                        if (isCData)
+                        {
+                            cDataFound = true;
+                        }
+
+                        var text = node is XText xtext ? xtext.Value : String.Empty;
+                        var nodeSB = new StringBuilder();
 
-        internal InnerTextContext()
+                        foreach (var c in text)
+                        {
+                            char? emit = c;
+
+                            // Replace contiguous whitespace with a single space.
+                            if (' ' == c || '\r' == c || '\n' == c || '\t' == c)
+                            {
+                                if (!inWhitespace)
+                                {
+                                    inWhitespace = true;
+                                    whitespaceFound = true;
+                                    emit = ' ';
+                                }
+                                else
+                                {
+                                    emit = null;
+                                }
+                            }
+                            else
+                            {
+                                inWhitespace = false;
+                            }
+
+                            if (emit.HasValue)
+                            {
+                                nodeSB.Append(emit);
+                            }
+                        }
+
+                        text = nodeSB.ToString();
+
+                        if (0 < text.Length)
+                        {
+                            text = text.Trim(whitespaceChars);
+
+                            if (0 == text.Length)
+                            {
+                                text = " ";
+                            }
+                        }
+
+                        sb.Append(text);
+                    }
+                }
+                var found = false;
+                collectedText = sb.ToString();
+
+                if (0 < collectedText.Length)
+                {
+                    found = true;
+                }
+
+                collectedText = collectedText.Trim(whitespaceChars);
+
+                if (whitespaceFound)
+                {
+                    found = true;
+                }
+
+                if (cDataFound)
+                {
+                    found = true;
+
+                    if (0 == collectedText.Length)
+                    {
+                        collectedText = " ";
+                    }
+                }
+
+                return found;
+            }
+
+            public bool CollectInnerTextWithTrailingWhitespaceAndCommentsForScriptFile(XElement element, out string collectedText)
             {
+                var value = String.Empty;
+                char[] whitespaceChars = { ' ', '\t', '\r', '\n' };
+                var nodes = element.Nodes().ToList();
+                var cDataFound = false;
+                var sb = new StringBuilder();
+
+                foreach (var node in nodes)
+                {
+                    if (XmlNodeType.Comment == node.NodeType)
+                    {
+                        this.Comments.Add(node);
+                    }
+                    else if (XmlNodeType.CDATA == node.NodeType || XmlNodeType.Text == node.NodeType)
+                    {
+                        var isCData = XmlNodeType.CDATA == node.NodeType;
+
+                        if (isCData)
+                        {
+                            cDataFound = true;
+                        }
+
+                        var text = node is XText xtext ? xtext.Value.Trim(whitespaceChars) : String.Empty;
+                        sb.Append(text);
+                    }
+                }
+
+                var found = false;
+
+                collectedText = sb.ToString();
+
+                if (0 < collectedText.Length)
+                {
+                    found = true;
+                }
+
+                collectedText = collectedText.Trim(whitespaceChars);
+
+                if (cDataFound)
+                {
+                    found = true;
+
+                    if (0 == collectedText.Length)
+                    {
+                        collectedText = " ";
+                    }
+                }
+
+                return found;
             }
         }