'System.Xml.XmlException: '[value]' is an unexpected token. The expected token is '"' or '''
I am having issues with the System.Xml.Linq XElement parser.
I am writing a console app that takes in an XML string as a parameter and it is throwing an error when parsing that string into an Xelement. The confusing thing is that the string itself is derived from the ".GetString() function of another Xelement.
The workflow is the following:
- LinuxXML.cs object builds xml element using System.Xml.Linq
- The object is passed into the console app as a string using a GetString() method call on the object
- The console app uses the xml string argument and calls another LinuxXML(string xml) constructor, as you can see here, using a string representation of the xml to rebuild the XElement.
- This is all driven by a powershell script
- The parsing of the xml in the console app (step 3), is throwing a parsing error with the Title message in the stack trace. It appears to be a parsing error concerning the first attribute of the root node.
The LinuxXML.cs class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace PerfTeamProjects.Shared
{
public class LinuxXML : ILinuxXML
{
private const string _USRNM = "username";
private const string _PWRD = "password";
private const string _MACHINE = "Machine";
private const string _NAME = "name";
private const string _IPADDRESS = "ipaddress";
private const string _SRVS = "Services";
private const string _SRV = "Service";
private const string _SRVQR = "query";
private string _username;
private string _password;
private XElement linMachXel = new XElement("LinuxMachines");
private List<IMachine> _machines = new List<IMachine>();
public string Username
{
get { return _username; }
set
{
_username = value;
linMachXel.SetAttributeValue(_USRNM, value);
}
}
public string Password
{
get { return _password; }
set
{
_password = value;
linMachXel.SetAttributeValue(_PWRD, value);
}
}
public IReadOnlyList<IMachine> Machines => _machines.AsReadOnly();
public LinuxXML()
{
}
public LinuxXML(string username, string password)
{
_username = username; _password = password;
linMachXel.SetAttributeValue(_USRNM, username);
linMachXel.SetAttributeValue(_PWRD, password);
}
public LinuxXML(XElement element) => linMachXel = element;
public LinuxXML(string xml)
{
linMachXel = XElement.Parse(xml, LoadOptions.PreserveWhitespace);
Username = linMachXel.Attribute(_USRNM).Value;
Password = linMachXel.Attribute(_PWRD).Value;
if (linMachXel.Descendants(_MACHINE)?.Count() > 0)
{
foreach (var machXel in linMachXel.Descendants(_MACHINE))
{
IMachine machine = MachineFactory.GetMachine();
machine.Name = machXel.Attribute(_NAME).Value;
machine.IPAddress = machXel.Attribute(_IPADDRESS).Value;
var servList = new List<IService>();
foreach (var serXel in machXel.Element(_SRVS).Descendants(_SRV))
{
IService service = ServiceFactory.GetService();
service.Name = serXel.Attribute(_NAME).Value;
service.LinQuery = serXel.Attribute(_SRVQR).Value;
servList.Add(service);
}
machine.Services = servList;
_machines.Add(machine);
}
}
else
{
Console.WriteLine("XML format for Linux machines incorret");
}
}
public void AddMachine(IMachine machine)
{
if (machine != null)
{
//add object data as element to linMachXels
var machXel = new XElement(_MACHINE);
machXel.SetAttributeValue(_NAME, machine.Name);
machXel.SetAttributeValue(_IPADDRESS, machine.IPAddress);
var servicesEl = new XElement(_SRVS);
foreach (var service in machine.Services)
{
var serviceEl = new XElement(_SRV);
serviceEl.SetAttributeValue(_NAME, service.Name);
serviceEl.SetAttributeValue(_SRVQR, service.LinQuery);
servicesEl.Add(serviceEl);
}
machXel.Add(servicesEl);
linMachXel.Add(machXel);
//add object to list
_machines.Add(machine);
}
}
public string GetXMLString()
{
return linMachXel.ToString(SaveOptions.DisableFormatting);
}
}
}
The Console (Program.cs) app attempting to run it:
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
using PerfTeamProjects.Shared;
using System.Timers;
namespace PerfConsoleApp
{
public class Program
{
public static void Main(string[] args)
{
try
{
LinuxXML block = new LinuxXML(args[0]);
string plinkPath = args[1] ?? "";
string outPath = args[2] ?? "";
//default 1 hour
int _duration = (Int32.TryParse(args[3], out _duration)) ? _duration * 1000 : 3600000;
//default 1 minutes
int _interval = (Int32.TryParse(args[4], out _interval)) ? _interval * 1000 : 60000;
DateTime initTime = DateTime.Now;
using (var timer = new Timer(_interval))
{
//connect action here
timer.Elapsed += (object sender, ElapsedEventArgs eEArgs) =>
{
if (eEArgs.SignalTime - initTime >= TimeSpan.FromSeconds(_duration)) Environment.Exit(0);
try
{
InitProcess(block, plinkPath, outPath);
}
catch (System.Xml.XmlException xmlException)
{
Console.WriteLine($"The xml exception occured at line {xmlException.LineNumber}, at pos {xmlException.LinePosition}");
Console.WriteLine($"The data is: {xmlException.Data}");
}
catch (Exception mainEx)
{
Console.WriteLine(mainEx);
}
};
timer.Start();
}
}
catch (System.Xml.XmlException xmlException)
{
Console.WriteLine($"XML Target Site: {xmlException.TargetSite}");
Console.WriteLine($"Line Number: {xmlException.LineNumber}");
Console.WriteLine($"Data: {xmlException.Data}");
Console.WriteLine($"Message: {xmlException.Message}");
Console.WriteLine($"SourcURI: {xmlException.SourceUri}");
Console.WriteLine($"AppSource: {xmlException.Source}");
}
}
private static void InitProcess(LinuxXML xml, string plinkPath, string outPath)
{
foreach (var machine in xml.Machines)
{
ProcessStartInfo psi = new ProcessStartInfo()
{
FileName = plinkPath,
Arguments = $"-ssh {xml.Username}@{machine.IPAddress} -pw {xml.Password}",
UseShellExecute = false
};
using (var p = new Process())
{
p.StartInfo = psi;
//Execute process
p.Start();
foreach (var service in machine.Services)
{
//StreamWriter
Console.WriteLine($"{service}");
}
}
}
}
}
}
The PowerShell Script calls for the models and injects them into the app:
<#
##################################################################################################################################
Author: #####
Last Updated: 07 Feberuary 2022
Description: Single Task Script for querying Linux machine service status
##################################################################################################################################
#>
$AutomationPath = Split-Path $PSScriptRoot
#.$AutomationPath\Powershells\ReadConfigurations.ps1
Add-Type -Path "$AutomationPath\Softwares\.NetClassLibraries\bin\PerfTeamProjects.Shared.dll"
#Get Interval and Durations in milliseconds
$interval = "6000"
$duration = "3600"
#Get Paths
$PlinkPath = ".$AutomationPath\Softwares\plink.exe"
$OutFilePath = " "
#$LinuxXML provided by Readconfigurations
$LinuxXML = New-Object -TypeName PerfTeamProjects.Shared.LinuxXML;
$LinuxXML.Username = "testUserName"
$LinuxXML.Password = "testPassWord"
#Add Machines
$Machine = New-Object -TypeName PerfTeamProjects.Shared.Machine;
$Machine.IPAddress = "192.168.555.555";
$Machine.Name = "testMachineName"
[xml]$xmlLinuxFile = Get-Content "$AutomationPath\ConfigFilesOther\LinuxServiceConfig.xml"
[System.Xml.XmlElement]$LinuxConfigElement = $xmlLinuxFile.LinuxMachines
$LinuxConfigServices = $LinuxConfigElement.SelectNodes("./Machine[@name='DCC']/Services/Service")
$MachineServices = New-Object System.Collections.Generic.List[PerfTeamProjects.Shared.IService]
foreach ($servConfig in $LinuxConfigServices) {
$Service = New-Object -TypeName PerfTeamProjects.Shared.Service
$Service.Name = $servConfig.getAttribute("name")
$Service.LinQuery = $servConfig.getAttribute("query")
#default service status is ServiceStatus.NotFound
$MachineServices.Add($Service)
}
$Machine.Services = $MachineServices;
$LinuxXML.AddMachine($Machine);
#Print string to console
$LinuxXml.GetXMLString() | Out-File $AutomationPath\SingleTaskScripts\linux.xml
.$AutomationPath\Softwares\.NetClassLibraries\bin\PerfConsoleApp.exe $LinuxXML.GetXMLString() $PlinkPath $OutFilePath $interval $duration
Write-Host "End of Script"
Towards the end of the powershell script it outputs the XML block it created to an xml file, as I was doing this to check what was getting passed, which is the following:
<LinuxMachines username="testUserName" password="testPassWord"><Machine name="testMachineName" ipaddress="192.168.555.555"><Services><Service name="DCC_Redis" query="sudo systemctl status dcc_redis.service" /></Services></Machine></LinuxMachines>
The error occurs at the last line of the PowerShell script where the .exe is called with five arguments: the xml string, two file paths, and two hardcoded integers as strings.
Ultimately the console app is to use the string data to make calls to a linux machine for service status but that's for later. I will have to rewrite everything if I can't get passed this parsing error.
Any and all help appreciated. Thank you.
Solution 1:[1]
So according to a console readout from within the Program.cs/Console app itself, PowerShell was consuming the double quotes from the XML string, and instead of passing the entire XML as a single string to arg[0] it instead broke up the string and applied it to multiple indexes of the args array in the Console app Main parameter.
I decided instead to output the XML string to a temporary XML file and instead of passing the XML string, I passed the string of that file's directory. I then Used the XElement Load() method on that directory string from within the Console app in place of the XElement Parse() method, which originally was used on the XML string that was not properly injected by PowerShell.
Rewritten Powershell script:
<#
##################################################################################################################################
Author: tsmelvin
Last Updated: 07 Feberuary 2022
Description: Single Task Script for querying Linux machine service status
##################################################################################################################################
#>
$AutomationPath = Split-Path $PSScriptRoot
#.$AutomationPath\Powershells\ReadConfigurations.ps1
Add-Type -Path "$AutomationPath\Softwares\.NetClassLibraries\bin\PerfTeamProjects.Shared.dll"
#Get Interval and Durations in milliseconds
$interval = "6000"
$duration = "3600"
#Get Paths
$PlinkPath = "$AutomationPath\Softwares\plink.exe"
$OutFilePath = " "
#$LinuxXML provided by Readconfigurations
$LinuxXML = New-Object -TypeName PerfTeamProjects.Shared.LinuxXML;
$LinuxXML.Username = "testUserName"
$LinuxXML.Password = "testPassWord"
#Add Machines
$Machine = New-Object -TypeName PerfTeamProjects.Shared.Machine;
$Machine.IPAddress = "192.168.555.555";
$Machine.Name = "testMachineName"
[xml]$xmlLinuxFile = Get-Content "$AutomationPath\ConfigFilesOther\LinuxServiceConfig.xml"
[System.Xml.XmlElement]$LinuxConfigElement = $xmlLinuxFile.LinuxMachines
$LinuxConfigServices = $LinuxConfigElement.SelectNodes("./Machine[@name='DCC']/Services/Service")
$MachineServices = New-Object System.Collections.Generic.List[PerfTeamProjects.Shared.IService]
foreach ($servConfig in $LinuxConfigServices) {
$Service = New-Object -TypeName PerfTeamProjects.Shared.Service
$Service.Name = $servConfig.getAttribute("name")
$Service.LinQuery = $servConfig.getAttribute("query")
#default service status is ServiceStatus.NotFound
$MachineServices.Add($Service)
}
$Machine.Services = $MachineServices;
$LinuxXML.AddMachine($Machine);
#Print string to console
$LinuxXml.GetXMLString() | Out-File $AutomationPath\ConfigFilesOther\TEMP_LINUX.xml
Start-Sleep -Seconds 2
$LinuxFilePath = "$AutomationPath\ConfigFilesOther\TEMP_LINUX.xml"
.$AutomationPath\Softwares\.NetClassLibraries\bin\PerfConsoleApp.exe $LinuxFilePath $PlinkPath $OutFilePath $interval $duration
Write-Host "End of Script"
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Travis |
