Search code examples
xmlnodespowershell-3.0game-developmentxsitype

Powershell & XML - nested nodes & xsi:type= blah blah blah


I run a dedicated server for an alpha/early access game called Space Engineers, occasionally they patch and break things and i am trying to create a powershell script to trouble shoot problems that may occur. I am ok with powershell, never messed with XML.

The first step is disabling all objects in the game so that they cause less calculations on the server. like so ->

$power = Get-Content 'F:\DedicatedServer\DataDir\SE Survival 2\Saves\VPS RC 1\SANDBOX_0_0_0_.sbs' -raw
$power = $power -replace "<Enabled>true</Enabled>", "<Enabled>false</Enabled>"
$power | Out-File 'F:\DedicatedServer\DataDir\SE Survival 2\Saves\VPS RC 1\SANDBOX_0_0_0_.sbs' -Encoding ascii

The second and third steps will be to bring all medical rooms and power sources (not shown) online. This is where i get into trouble.

[xml]$myXML = Get-Content 'F:\DedicatedServer\DataDir\SE Survival 2\Saves\VPS RC 1\SANDBOX_0_0_0_.sbs'

$ns = New-Object System.Xml.XmlNamespaceManager($myXML.NameTable)
$ns.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance")

$node = $myXML.SelectNodes('//SectorObjects/MyObjectBuilder_EntityBase/CubeBlocks/MyObjectBuilder_CubeBlock‌​[@xsi:type="MyObjectBuilder_MedicalRoom"]/Enabled', $ns) | % {    
    #set all med bays to be enabled.

    $switch= Select-XML -XML $myXML -XPath $node
    $switch.Node.InnerText = $switch.Node.InnerText.Replace("false", "true")

    $myXML.Save('F:\DedicatedServer\DataDir\SE Survival 2\Saves\VPS RC 1\SANDBOX_0_0_0_.sbs')
}

Here is an excerpt of the XML file with non relevant data removed -

<?xml version="1.0"?>
<MyObjectBuilder_Sector xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <Position>
  </Position>
  <SectorEvents> 
  </SectorEvents>
  <AppVersion>1044014</AppVersion>
  <SectorObjects>
    <MyObjectBuilder_EntityBase xsi:type="MyObjectBuilder_CubeGrid">
      <EntityId>72230476941025901</EntityId>
      <PersistentFlags>CastShadows InScene</PersistentFlags>
      <PositionAndOrientation>
      </PositionAndOrientation>
      <GridSizeEnum>Large</GridSizeEnum>
      <CubeBlocks>
        <MyObjectBuilder_CubeBlock xsi:type="MyObjectBuilder_CubeBlock">          
        </MyObjectBuilder_CubeBlock>
        <MyObjectBuilder_CubeBlock xsi:type="MyObjectBuilder_MedicalRoom">
          <SubtypeName>LargeMedicalRoom</SubtypeName>
          <EntityId>72107097601717796</EntityId>
          <Min x="4" y="1" z="-1" />
          <BlockOrientation Forward="Forward" Up="Up" />
          <ColorMaskHSV x="0" y="0.15" z="0.25" />
          <Owner>144233151425053409</Owner>
          <ShareMode>Faction</ShareMode>
          <CustomName>Lurch Enterprises</CustomName>
          <ShowOnHUD>false</ShowOnHUD>
          <Enabled>false</Enabled>
          <SteamUserId>0</SteamUserId>
        </MyObjectBuilder_CubeBlock>

the XML file is not updating with the desired changes. Which are change every instance of

<MyObjectBuilder_CubeBlock xsi:type="MyObjectBuilder_MedicalRoom">
      <Enabled>false</Enabled>

to true.

The current errors are:

Select-Xml : Cannot validate argument on parameter 'XPath'. The argument is null or empty. Provide an argument that is not null or empty, and then try the command again.
At line:16 char:44
+     $switch= Select-XML -XML $myXML -XPath $node
+                                            ~~~~~
    + CategoryInfo          : InvalidData: (:) [Select-Xml], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.PowerShell.Commands.SelectXmlCommand

You cannot call a method on a null-valued expression.
At line:17 char:5
+     $switch.Node.InnerText = $switch.Node.InnerText.Replace("false", "true")
+     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : InvokeMethodOnNull

and answered! here is the final product that does what i wanted.

$filePath = '<your save file path here>\SANDBOX_0_0_0_.sbs'

#troubleshooting script
#switch EVERYTHING off
$power = Get-Content $filePath -raw
$power = $power -replace "<Enabled>true</Enabled>", "<Enabled>false</Enabled>"
$power | Out-File $filePath -Encoding ascii

#turn on medbays, reactors, batteries, solar panels ONLY
[xml]$myXML = Get-Content $filePath
$ns = New-Object System.Xml.XmlNamespaceManager($myXML.NameTable)
$ns.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance")
$nodes = $myXML.SelectNodes("//SectorObjects/MyObjectBuilder_EntityBase/CubeBlocks/MyObjectBuilder_CubeBlock[@xsi:type='MyObjectBuilder_MedicalRoom']/Enabled|//SectorObjects/MyObjectBuilder_EntityBase/CubeBlocks/MyObjectBuilder_CubeBlock[@xsi:type='MyObjectBuilder_Reactor']/Enabled|//SectorObjects/MyObjectBuilder_EntityBase/CubeBlocks/MyObjectBuilder_CubeBlock[@xsi:type='MyObjectBuilder_BatteryBlock']/Enabled|//SectorObjects/MyObjectBuilder_EntityBase/CubeBlocks/MyObjectBuilder_CubeBlock[@xsi:type='MyObjectBuilder_SolarPanel']/Enabled", $ns)
ForEach($node in $nodes)
{
$node.InnerText = "true"
}
$myXML.Save($filePath)

Solution

  • To be able to use xsi prefix in your XPath you need to register prefix-to-namespace URI mapping to XmlNamespaceManager, then pass the XmlNamespaceManager to SelectNodes() method :

    .....
    $ns = New-Object System.Xml.XmlNamespaceManager($myXML.NameTable)
    $ns.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance")
    $node = $myXML.SelectNodes("your xpath that contains xsi prefix here", $ns)
    .....
    

    UPDATE :

    Turned out there was hidden character before [ in your XPath that's why you got "invalid token" error. I'm not familiar with PowerShell specific syntax, but this worked fine for me :

    [xml]$myXML = Get-Content "F:\DedicatedServer\DataDir\SE Survival 2\Saves\VPS RC 1\SANDBOX_0_0_0_.sbs"
    
    $ns = New-Object System.Xml.XmlNamespaceManager($myXML.NameTable)
    $ns.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance")
    $nodes = $myXML.SelectNodes("//SectorObjects/MyObjectBuilder_EntityBase/CubeBlocks/MyObjectBuilder_CubeBlock[@xsi:type='MyObjectBuilder_MedicalRoom']/Enabled", $ns)
    ForEach($node in $nodes)
    {
        $node.InnerText = "true"
    }
    $myXML.Save("F:\DedicatedServer\DataDir\SE Survival 2\Saves\VPS RC 1\SANDBOX_0_0_0_.sbs")