You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PowerShell递归解析XML转CSV遇无效字符转换错误求助

PowerShell递归解析XML转CSV时的无效字符错误处理

现有实现代码

第一段通用XML扁平化代码

function Flatten-Xml ([System.Xml.XmlNode]$Xml, [Parameter(DontShow)]$ParentName) {
    if (!$ParentName) { $Properties = [Ordered]@{} }
    $Xml |Get-Member -MemberType 'Property' |ForEach-Object {
        $Name = if ($ParentName) { "$ParentName.$($_.Name)" } else { $_.Name }
        $Value = $Xml.$($_.Name)
        if ($Value -is [System.Xml.XmlElement]) { Flatten-Xml $Value $Name }
        elseif ($Value -is [String]) { $Properties[$Name] = $Value }
    }
    if (!$ParentName) { [PSCustomObject]$Properties }
}

function UnifyProperties {
  $Names = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)
  $InputCollected = @($Input)
  $InputCollected.ForEach({ 
    foreach ($Name in $_.psobject.Properties.Name) { $Null = $Names.Add($Name) }
  })
  $inputCollected | Select-Object @($Names)
}

$ListOfTasks = (Get-ChildItem -File -Path .\ -Recurse).fullname
$Final = @()
$ListOfTasks | 

foreach {

    $xml = [xml](get-content $_)
    $Final += Flatten-Xml $Xml
}

$Final | UnifyProperties | Export-Csv -Path .\01Test.csv -NoTypeInformation

第二段针对Windows任务计划XML的专用解析代码

[CmdletBinding()]
param (
  [Parameter(Mandatory = $true)] $PathToXML
)

$ListOfTasks = (Get-ChildItem -File -Path $PathToXML -Recurse).fullname
$ListOfTasks | foreach {
    $ModifiedTime = (gci -Path $_).LastWriteTimeUtc.toString('yyyy-MM-ddTHH:mm:ss')+':00z'
    $xmlFile = [xml](get-content "$_")
    $Date = $xmlFile.ChildNodes.RegistrationInfo.Date
    $Author = $xmlFile.ChildNodes.RegistrationInfo.Author
    $Description = $xmlFile.ChildNodes.RegistrationInfo.Description
    $URI = $xmlFile.ChildNodes.RegistrationInfo.URI
    $Principals = $xmlFile.ChildNodes.Principals.Principal.UserId
    $LogonType = $xmlFile.ChildNodes.Principals.Principal.LogonType
    $Enabled = $xmlFile.ChildNodes.Settings.Enabled
    $Action = $xmlFile.ChildNodes.Actions.Exec.Command
    $Arguments = $xmlFile.ChildNodes.Actions.Exec.Arguments

    $xmlFile.ChildNodes[1] | 
    ForEach-Object {
                [PSCustomObject]@{
                    TaskFile_LastModifiedTime = $ModifiedTime
                    Registration_Date = $Date
                    Author = $Author
                    Description = $Description
                    Task_Name = $URI
                    Principals_UserContext = $Principals
                    LogonType = $LogonType
                    Enabled = $Enabled
                    Action_Arguments = $Action + " " + $Arguments
                    #Arguments = $Arguments
                }
            } 
} | Export-Csv -Path .\Parsed_Tasks_XML.csv -NoTypeInformation

执行时的错误信息

Cannot convert value "System.Object[]" to type "System.Xml.XmlDocument". Error: "'', hexadecimal value 0x08, is an invalid character. Line 1, position 1."
At C:\Users\Vikas\Desktop\0Sophos-P520\Research\Parse_TaskXMLs_Verbose.ps1:27 char:5
+     $xml = [xml](get-content $_)
+     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [], RuntimeException
    + FullyQualifiedErrorId : InvalidCastToXmlDocument

问题分析与解决方案

问题根源

错误提示的0x08是退格符,属于XML规范中禁止的控制字符。虽然Windows任务计划导出的XML通常规范,但部分文件可能因为导出异常、编辑残留等原因包含这类无效字符,且原代码没有错误捕获机制,无法定位具体出错文件。

解决步骤

1. 添加错误捕获,定位出错文件

修改文件处理逻辑,加入try/catch块,捕获错误并输出出错文件路径:

# 以第一段代码为例,修改foreach部分
$ListOfTasks | foreach {
    $filePath = $_
    try {
        $xml = [xml](get-content $filePath)
        $Final += Flatten-Xml $Xml
        Write-Host "Successfully processed: $filePath"
    }
    catch {
        Write-Error "Failed to process file: $filePath`nError: $_"
    }
}

2. 清理XML中的无效字符

在加载XML前,先读取文件内容并过滤掉XML禁止的控制字符,保留合法的制表符、换行、回车:

# 替换原代码中$xml = [xml](get-content $_)的部分
$content = Get-Content $filePath -Raw
$cleanContent = $content -replace '[\x00-\x08\x0B\x0C\x0E-\x1F]',''
$xml = [xml]$cleanContent

3. 优化专用解析代码的健壮性

针对任务计划XML的解析代码,增加节点存在性检查,避免因部分XML结构缺失导致额外错误:

# 示例:检查RegistrationInfo节点是否存在
if ($xmlFile.ChildNodes.RegistrationInfo) {
    $Date = $xmlFile.ChildNodes.RegistrationInfo.Date
    $Author = $xmlFile.ChildNodes.RegistrationInfo.Author
    $Description = $xmlFile.ChildNodes.RegistrationInfo.Description
    $URI = $xmlFile.ChildNodes.RegistrationInfo.URI
}
else {
    $Date = $Author = $Description = $URI = $null
}

完整修改后的第一段代码示例

function Flatten-Xml ([System.Xml.XmlNode]$Xml, [Parameter(DontShow)]$ParentName) {
    if (!$ParentName) { $Properties = [Ordered]@{} }
    $Xml |Get-Member -MemberType 'Property' |ForEach-Object {
        $Name = if ($ParentName) { "$ParentName.$($_.Name)" } else { $_.Name }
        $Value = $Xml.$($_.Name)
        if ($Value -is [System.Xml.XmlElement]) { Flatten-Xml $Value $Name }
        elseif ($Value -is [String]) { $Properties[$Name] = $Value }
    }
    if (!$ParentName) { [PSCustomObject]$Properties }
}

function UnifyProperties {
  $Names = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)
  $InputCollected = @($Input)
  $InputCollected.ForEach({ 
    foreach ($Name in $_.psobject.Properties.Name) { $Null = $Names.Add($Name) }
  })
  $inputCollected | Select-Object @($Names)
}

$ListOfTasks = (Get-ChildItem -File -Path .\ -Recurse).fullname
$Final = @()
$ListOfTasks | foreach {
    $filePath = $_
    try {
        $content = Get-Content $filePath -Raw
        $cleanContent = $content -replace '[\x00-\x08\x0B\x0C\x0E-\x1F]',''
        $xml = [xml]$cleanContent
        $Final += Flatten-Xml $Xml
        Write-Host "Processed: $filePath"
    }
    catch {
        Write-Error "Failed to process: $filePath`nError detail: $_"
    }
}

$Final | UnifyProperties | Export-Csv -Path .\01Test.csv -NoTypeInformation

内容的提问来源于stack exchange,提问作者Origami

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 15:10:26