PowerShell中ConvertTo-Json深度超限及疑似死锁问题求助
问题背景
从包含Azure策略列表的JSON文件出发,通过PowerShell获取内置策略并转换为对象进行配置修改(添加ASC元数据、处理effect参数等),但将修改后的PowerShell对象转换回JSON时,出现以下警告:
WARNING: Resulting JSON is truncated as serialization has exceeded the set depth of 100
同时脚本运行无进展,疑似陷入死锁。需排查是否可绕过-Depth参数限制,或代码是否存在导致无限分支的问题。
初始策略列表JSON
[ { "displayName": "API Management service should use a SKU that supports virtual networks", "effect": "Audit" }, { "displayName": "Configure API Management services to disable public network access", "effect": "Disabled" }, { "displayName": "API Management services should disable public network access", "effect": "AuditIfNotExists" } ]
当前PowerShell配置代码
$Policies = az policy definition list | ConvertFrom-Json $BuiltIns = New-Object System.Collections.Generic.List[System.Object] # Loop through powershell objects and trim strings before adding them to existing array Write-Host "Extracting all buit-in policy ids:" foreach($Policy in $Policies ){ if($Policy.policyType.ToString() -eq "BuiltIn"){ $BuiltIns.Add($Policy) } } $BuiltIns = $BuiltIns | Sort-Object -Property metadata.category $ChosenPolicies = Get-Content "C:\PATHTOJSONFILE\policyList.json" | ConvertFrom-Json Write-Host "Duplicating the chosen policies into .json files" ##Looping through all existing built-in policies foreach($BuiltIn in $BuiltIns){ ##Testing built-in policies against the chosen policies foreach($Policy in $ChosenPolicies){ if($BuiltIn.displayName -eq $Policy.displayName){ $Path = "C:\PATHTOCONFIGDIRECTORY\policies-json\" + $Policy.displayName + ".json" ## Turning JSON data from policy into powershell objects $CurrentObject = az policy definition show --name $BuiltIn.name | ConvertFrom-Json ##Adding metadata that enables custom policies to be viewed in security center $CurrentObject.metadata | add-member -Name "ASC" -value ("true") -MemberType NoteProperty ##Testing if JSON member "effect" exists ##Testing if parameters has member effect ##Testing if parameters equals NULL if($null -eq $CurrentObject.parameters -or !$CurrentObject.parameters -or $CurrentObject.parameters -eq ""){ if($null -eq $CurrentObject.parameters ) { $CurrentObject | add-member -Name "parameters" -value ({}) -MemberType NoteProperty -Force } if(!$CurrentObject.parameters.effect){ $memberEffect= [PSCustomObject]@{ type = "string" } $memberMetadata= [PSCustomObject]@{ description="Enable or disable the execution of the policy" displayName= "Effect" } ##Adding member effect $CurrentObject.parameters | add-member -Name "effect" -value ($memberEffect) -MemberType NoteProperty $CurrentObject.parameters.effect | add-member -Name "metadata" -value ($memberMetadata) -MemberType NoteProperty } if($CurrentObject.parameters.effect.defaultValue){ $CurrentObject.parameters.effect.defaultValue = $Policy.effect } } if($CurrentObject.policyRule.then.effect){ ##Sets effect to a parameter that later can be specified $CurrentObject.policyRule.then.effect = "[parameters('effect')]" } ##Removing illegal symbols from filename if($CurrentObject.displayName.Contains("[Preview]:")){ $Policy.displayName = $Policy.displayName.ToString().Replace('[Preview]:', 'Preview') $Path = "C:\PATHTOCONFIGDIRECTORY\policies-json\" + $Policy.displayName + ".json" ##Converting object to JSON and pushing it into its own .JSON file $CurrentObject | ConvertTo-Json -depth 100 | Out-File -Path $Path } else { $CurrentObject | ConvertTo-Json -depth 100 | Out-File -LiteralPath $Path } } } }
问题分析与解决方案
1. 脚本“死锁”实为性能瓶颈
当前代码使用双重嵌套循环:遍历所有内置策略(数量可能达数千),再逐个与选中策略匹配,时间复杂度为O(n*m),导致脚本运行极慢,看起来像死锁。
优化方案:用哈希表存储选中策略的displayName与对应effect,将查找操作降为O(1):
# 预先构建选中策略的哈希表 $chosenPolicyMap = @{} foreach ($policy in $ChosenPolicies) { $chosenPolicyMap[$policy.displayName] = $policy.effect } # 遍历内置策略时直接查哈希表,移除内层循环 foreach ($BuiltIn in $BuiltIns) { if ($chosenPolicyMap.ContainsKey($BuiltIn.displayName)) { $targetEffect = $chosenPolicyMap[$BuiltIn.displayName] $Path = "C:\PATHTOCONFIGDIRECTORY\policies-json\" + $BuiltIn.displayName + ".json" $CurrentObject = az policy definition show --name $BuiltIn.name | ConvertFrom-Json # 添加ASC元数据 $CurrentObject.metadata | Add-Member -Name "ASC" -Value "true" -MemberType NoteProperty # 处理effect参数逻辑 if ($null -eq $CurrentObject.parameters -or !$CurrentObject.parameters) { $CurrentObject | Add-Member -Name "parameters" -Value @{} -MemberType NoteProperty -Force } if (!$CurrentObject.parameters.effect) { $memberEffect = [PSCustomObject]@{ type = "string" } $memberMetadata = [PSCustomObject]@{ description = "Enable or disable the execution of the policy" displayName = "Effect" } $CurrentObject.parameters | Add-Member -Name "effect" -Value $memberEffect -MemberType NoteProperty $CurrentObject.parameters.effect | Add-Member -Name "metadata" -Value $memberMetadata -MemberType NoteProperty } if ($CurrentObject.parameters.effect.defaultValue) { $CurrentObject.parameters.effect.defaultValue = $targetEffect } if ($CurrentObject.policyRule.then.effect) { $CurrentObject.policyRule.then.effect = "[parameters('effect')]" } # 文件名处理与序列化 if ($CurrentObject.displayName.Contains("[Preview]:")) { $cleanDisplayName = $BuiltIn.displayName.Replace('[Preview]:', 'Preview') $Path = "C:\PATHTOCONFIGDIRECTORY\policies-json\" + $cleanDisplayName + ".json" } $CurrentObject | ConvertTo-Json -Depth 100 | Out-File -LiteralPath $Path } }
2. 序列化深度限制问题
PowerShell原生ConvertTo-Json的-Depth参数无硬性上限,但Azure策略对象的嵌套深度可能超过100,导致截断警告。
解决方案:
方案A:使用第三方JSON序列化库
Newtonsoft.Json(PowerShell模块为PowerShellJsonSerializer)无默认深度限制,支持更大的序列化深度:
# 安装模块(仅需一次) Install-Module -Name PowerShellJsonSerializer -Scope CurrentUser -Force # 导入模块并序列化 Import-Module PowerShellJsonSerializer $jsonContent = ConvertTo-JsonNewtonsoft -InputObject $CurrentObject -Depth 1000 $jsonContent | Out-File -LiteralPath $Path
方案B:裁剪不必要的嵌套属性
如果策略对象中存在无需保留的深层属性(如某些冗余元数据),可在序列化前移除,减少嵌套深度:
# 示例:移除不必要的深层属性 $CurrentObject.metadata.Remove('someRedundantProperty') $CurrentObject | ConvertTo-Json -Depth 100 | Out-File -LiteralPath $Path
3. 排查循环引用问题
若对象存在循环引用(如属性引用父对象),会导致序列化时无限递归,引发假死。当前代码中添加的元数据均为新建PSCustomObject,无循环引用风险,但可通过以下脚本检测对象嵌套深度:
function Get-ObjectDepth { param($Object, $CurrentDepth = 0) if ($null -eq $Object -or $Object -is [ValueType] -or $Object -is [string]) { return $CurrentDepth } $maxDepth = $CurrentDepth foreach ($property in $Object.PSObject.Properties) { $depth = Get-ObjectDepth -Object $property.Value -CurrentDepth ($CurrentDepth + 1) if ($depth -gt $maxDepth) { $maxDepth = $depth } } return $maxDepth } # 检测当前策略对象的嵌套深度 Get-ObjectDepth -Object $CurrentObject
内容的提问来源于stack exchange,提问作者Caspett

