Search code examples
xmlpowershellexport-to-csv

XmlNode.SelectSingleNode recursively to export CSV


I'm trying to export a csv with FileZillaServer data from XML config. It works as exepcted, except for the condition Where-Object due to it stopped on the first block with the same name <Permissions>. I'd like to get Permission Dir value only if it's set to HomeDirectory "IsHome">1

XMl contains:

<FileZillaServer>
    <Users>
        <User Name="USER1">
          <Option Name="Pass">e1923304e508b24dc0</Option>
          <Option Name="Salt"></Option>
          <Option Name="Group"></Option>
          <Option Name="Bypass server userlimit">1</Option>
          <Option Name="User Limit">0</Option>
          <Option Name="IP Limit">0</Option>
          <Option Name="Enabled">0</Option>
          <Option Name="Comments">Comment 1</Option>
          <Option Name="ForceSsl">0</Option>
          <IpFilter>
                <Disallowed />
                <Allowed />
          </IpFilter>
          <Permissions>
                <Permission Dir="C:\folder1\subfolder1">
                    <Option Name="FileRead">1</Option>
                    <Option Name="FileWrite">1</Option>
                    <Option Name="FileDelete">1</Option>
                    <Option Name="FileAppend">0</Option>
                    <Option Name="DirCreate">0</Option>
                    <Option Name="DirDelete">0</Option>
                    <Option Name="DirList">1</Option>
                    <Option Name="DirSubdirs">1</Option>
                    <Option Name="IsHome">0</Option>
                    <Option Name="AutoCreate">0</Option>
                </Permission>
                <Permission Dir="C:\folder1\subfolder2">
                    <Option Name="FileRead">1</Option>
                    <Option Name="FileWrite">0</Option>
                    <Option Name="FileDelete">0</Option>
                    <Option Name="FileAppend">0</Option>
                    <Option Name="DirCreate">0</Option>
                    <Option Name="DirDelete">0</Option>
                    <Option Name="DirList">1</Option>
                    <Option Name="DirSubdirs">1</Option>
                    <Option Name="IsHome">1</Option>
                    <Option Name="AutoCreate">0</Option>
                </Permission>
              </Permissions>
                    <SpeedLimits DlType="0" DlLimit="10" ServerDlLimitBypass="0" UlType="0" UlLimit="10" ServerUlLimitBypass="0">
                <Download />
                <Upload />
          </SpeedLimits>
        </User>
    </Users>
</FileZillaServer>

I'm using this code:

$xmlFilePath = "C:\FileZilla Server.xml"
$newcsv = "C:\newcsv.csv"
$xml = [xml](Get-Content $xmlFilePath)
 
$results = foreach ($user in $xml.FileZillaServer.Users.User){                                                
                [pscustomobject]@{
                User = $user.Name
                Comment = $Comments = $user.SelectSingleNode("Option[@Name='Comments']").InnerText = $user.SelectSingleNode("Option[@Name='Comments']").InnerText
                ForceSSl = $ForceSSL = $user.SelectSingleNode("Option[@Name='ForceSsl']").InnerText = $user.SelectSingleNode("Option[@Name='ForceSsl']").InnerTex
                Enabled = $Enabled = $user.SelectSingleNode("Option[@Name='Enabled']").InnerText = $user.SelectSingleNode("Option[@Name='Enabled']").InnerText                          
                HomeDir = $user.Permissions.SelectSingleNode("Permission[@Dir]").Dir | Where-Object {$user.Permissions.SelectSingleNode("Permission[@Dir]/Option[@Name='IsHome']").InnerText -eq 1} 
                                 }                                                          
} $results | export-csv $newcsv -NoTypeInformation

Solution

  • You can do the following:

    $xmlFilePath = "C:\FileZilla Server.xml"
    $xml = [xml](Get-Content $xmlFilePath)
     
    $results = foreach ($user in $xml.FileZillaServer.Users.User){                                                
                   [pscustomobject]@{                        
                        HomeDir = $user.Permissions.SelectSingleNode("Permission[@Dir][./Option[@Name = 'IsHome'] = '1']").Dir
                   } 
    }                                                          
    

    The XPath predicate [./Option[@Name = 'IsHome'] = '1'] creates a condition where the current node (Permission) contains a child node Option with an attribute called Name. Name must have a value of IsHome and the text for that Option node must be 1.


    The reason the HomeDir property was not set as expected is because of how the selection conditions are ordered.

    $user.Permissions.SelectSingleNode("Permission[@Dir]").Dir selects the first node that is named Permission, which contains an attribute named Dir. When that node is selected, the property Dir is referenced on that PowerShell object. Ultimately, you are returning the string that represents Dir and then piping that to Where-Object. Any piped Where-Object conditions after that will only filter that string further.

    If you wanted to rely on Where-Object to filter, you can do something like the following:

    ($user.Permissions.SelectNodes("Permission[@Dir]") | where {
        $_.SelectSingleNode("Option[@Name = 'IsHome']").Innertext -eq 1}).Dir
    

    Notice how the Dir property value is retrieved after the node, attribute, and text conditions are applied. SelectNodes() is used because we need to send all potential nodes to Where-Object for it to filter further.