PowerShell foreach循环存数组为空、无法生成HTML邮件正文的问题
问题根因排查
数组赋值为空的核心原因
- 两种执行场景的输出流逻辑差异:直接在控制台执行脚本时,模块返回的有效用户对象会直接输出到控制台,错误不会影响已生成内容的展示;但当把foreach循环整体用
@()包裹赋值给变量时,循环内部抛出的非终止错误会直接清空当前输出流的收集结果,最终得到空数组。 - 跨租户调用
Get-MsolUser时未添加错误屏蔽,遇到无合作权限、权限过期的客户租户时,MSOnline模块抛出的非终止错误会直接中断当前循环块的标准输出流,进一步加剧输出收集失效的问题。 - 原代码中
$_.isLicensed -eq "TRUE"的判断存在类型不匹配问题:isLicensed属性本身是布尔类型,部分版本的MSOnline模块会因为类型不匹配静默返回空结果。 - 原代码直接将
select UserPrincipalName输出的PSCustomObject对象存入数组,未提取纯属性值,在HTML渲染时会触发默认对象字符串化逻辑,导致内容挤成一行。
输出格式混乱挤在同一行的原因
PowerShell对非字符串类型的对象做隐式字符串转换时,会默认输出对象的类型全称,不会逐字段换行展示。控制台场景下有专属格式化引擎做表格化渲染,所以能看到正常的用户列表;但直接将这类对象数组拼入HTML邮件正文时,没有格式化引擎做适配,所有内容会被压缩成单个字符串块,无法按列表展示。
可直接复用的修正代码
Connect-MsolService -Credential $Credential $customers = Get-MsolPartnerContract -All $nonCompliantUsers = @() foreach ($customer in $customers) { # 跳过指定匹配域名的租户 if ($customer.defaultdomainname -like "*Domain1*" -or $customer.defaultdomainname -like "*DomainABC*") { continue } # 租户级用户查询,加错误屏蔽避免中断循环 $tenantRiskUsers = Get-MsolUser -All -EnabledFilter EnabledOnly -TenantId $($customer.TenantId) ` -ErrorAction SilentlyContinue | Where-Object { $_.StrongAuthenticationRequirements.State -eq $null -and $_.isLicensed -eq $true } # 逐行提取纯字符串UPN存入数组,不保留对象 foreach ($user in $tenantRiskUsers) { $nonCompliantUsers += $user.UserPrincipalName } } # 拼接HTML格式的邮件正文,适配邮件渲染规则 $htmlMailBody = @" <h3>跨租户未配置MFA的已授权风险用户清单</h3> <p>共排查到不符合合规要求的用户:$($nonCompliantUsers.Count) 个</p> <ul> <li>$($nonCompliantUsers -join "</li><li>")</li> </ul> "@
实操注意事项
- 不要依赖
@(foreach(...))的隐式数组收集逻辑处理带外部模块调用的循环:模块抛出的非终止错误会直接干扰输出流的收集结果,显式初始化数组、逐行追加值的方式稳定性更高。 - 生成HTML邮件内容时,必须逐行拼接带标签的纯文本内容,不要直接将PowerShell对象数组隐式转为字符串传入正文。
- 所有跨租户的MSOnline调用必须加
-ErrorAction SilentlyContinue,适配CSP合作场景下必然存在的租户权限过期、合作关系解除的场景,避免单个租户的查询错误打断全量数据收集。
内容的提问来源于stack exchange,提问作者Pat
相关产品推荐
相关产品推荐

