PowerShell foreach访问特殊字符属性及MSSQL数据写入问题
问题1:带特殊字符/空格的属性访问语法
PowerShell中直接用.访问属性时,属性名不能包含空格、特殊符号(%、括号等),否则会被解析成运算符触发报错。正确访问方式是将属性名用单/双引号包裹后接在点运算符后,或放在方括号内,示例:
# 错误写法(会触发%运算符缺值的报错) $cpu_percent_1=$CPUPer.CPU % $memory_1=$CPUPer.Memory (MB) # 正确写法1:单引号包裹属性名接在点后 $cpu_percent_1 = $CPUPer.'CPU %' $memory_1 = $CPUPer.'Memory (MB)' # 正确写法2:方括号包裹属性名索引访问 $cpu_percent_1 = $CPUPer['CPU %']
优先用单引号包裹属性名,避免双引号下的变量意外解析。
问题2:现有脚本逻辑问题排查
当前脚本存在5个核心问题,直接运行无法正常入库:
- 错误将格式化输出作为数据源:
Format-Table返回的是用于控制台显示的格式对象,不是原始结构化进程数据,把它的结果赋值给$CPUPercent后,遍历根本取不到Name、CPU这些真实属性值。 - 重复采集+变量覆盖:第一行定义完
$CPUPercent(CPU占比计算属性)、$CPU(CPU计算属性)后,直接执行了一次Get-Process输出到控制台,第二行又重复执行一次Get-Process,还把Format-Table的结果赋值给$CPUPercent,直接覆盖了之前定义的计算属性,属于无效重复操作。 - SQL拼接风险:直接用变量拼接INSERT语句,遇到进程名、描述中带单引号的内容会直接触发SQL语法错误,同时存在SQL注入风险。
- 边界值未处理:刚启动的进程运行总时长可能为0,计算CPU占比时会触发除以0的错误,返回NaN无效值。
- 入库效率极低:逐进程调用
Invoke-Sqlcmd会反复创建数据库连接、提交单条语句,进程数量多时性能很差。
数据入库优化方案
核心优化点
- 采集阶段移除所有
Format-*命令,仅保留原始结构化数据,需要控制台展示时单独调用格式化输出 - 新增进程运行时长判断,避免除以0错误
- 统一数值精度,和MSSQL表字段类型匹配
- 用SqlBulkCopy批量提交代替逐行字符串拼接,既避免语法错误,也能把入库效率提升10倍以上
- 修正特殊字符属性的访问写法,处理描述为空的场景避免入库报错
优化后参考脚本
# 定义计算属性 $cpuCal = @{ Name = 'CPU' Expression = { [Math]::Round($_.CPU, 2) } } $cpuPercentCal = @{ Name = 'CPU %' Expression = { $totalSec = (New-TimeSpan -Start $_.StartTime).TotalSeconds # 避免除以0错误,进程运行时长小于1秒时CPU占比记为0 if ($totalSec -lt 1) { return 0 } [Math]::Round(($_.CPU * 100 / $totalSec), 2) } } $memoryCal = @{ Name = 'Memory (MB)' Expression = { [Math]::Round(($_.WorkingSet64 / 1MB), 2) } } # 采集原始进程数据(不要加Format-Table) $processData = Get-Process | Select-Object -Property Name, $cpuCal, $cpuPercentCal, $memoryCal, Description | Sort-Object -Property CPU -Descending # 控制台展示采集结果(单独调用格式化) $processData | Format-Table -AutoSize # 数据库连接配置 $dbServer = 'KILIKOOD-PC\MSSQLSERVER,1433' $dbName = 'Fantasy' $dbUser = 'sa' $dbPwd = 'test123' $connStr = "Server=$dbServer;Database=$dbName;User Id=$dbUser;Password=$dbPwd;TrustServerCertificate=True;" # 用SqlBulkCopy批量入库 $bulkCopy = New-Object System.Data.SqlClient.SqlBulkCopy($connStr) $bulkCopy.DestinationTableName = "[dbo].[ServiceTable]" # 映射数据源字段和数据库表字段 $bulkCopy.ColumnMappings.Add("Name", "Name") | Out-Null $bulkCopy.ColumnMappings.Add("CPU", "CPU") | Out-Null $bulkCopy.ColumnMappings.Add("CPU %", "CPU_Perc") | Out-Null $bulkCopy.ColumnMappings.Add("Memory (MB)", "Memory_MB") | Out-Null $bulkCopy.ColumnMappings.Add("Description", "Description") | Out-Null # 转成DataTable格式用于批量导入 $dataTable = New-Object System.Data.DataTable $dataTable.Columns.Add("Name", [string]) | Out-Null $dataTable.Columns.Add("CPU", [decimal]) | Out-Null $dataTable.Columns.Add("CPU %", [decimal]) | Out-Null $dataTable.Columns.Add("Memory (MB)", [decimal]) | Out-Null $dataTable.Columns.Add("Description", [string]) | Out-Null foreach ($proc in $processData) { $row = $dataTable.NewRow() $row["Name"] = $proc.Name $row["CPU"] = $proc.CPU $row["CPU %"] = $proc.'CPU %' $row["Memory (MB)"] = $proc.'Memory (MB)' $row["Description"] = if ($proc.Description) { $proc.Description } else { [DBNull]::Value } $dataTable.Rows.Add($row) } # 执行批量写入 $bulkCopy.WriteToServer($dataTable) $bulkCopy.Close()
内容的提问来源于stack exchange,提问作者clodhead
相关产品推荐
相关产品推荐

