我对我看到的一些行为感到有些困惑。
以下代码旨在查找和替换注册表中的多个字符串。
$keys = @(gci -Path hkcu:\ -recurse -ErrorAction SilentlyContinue)
foreach ($key in $keys)
{
foreach ($vname in $key.GetValueNames())
{
$val = $key.GetValue($vname, $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
if ($val -like "c:\users\administrator*")
{
$nval = $val -replace "(?i:^(C:\\users\\Administrator))", "%USERPROFILE%"
write-host "$key\$vname=$val -> $nval"
((Get-Item $Key.PSParentPath).OpenSubKey($Key.PSChildName, "True")).SetValue($vname, $nval, $key.GetValueKind($vname))
}
}
}
当注册表值类型为REG_MULTI_SZ时,我继续收到以下错误消息。 Exception calling "SetValue" with "3" argument(s): "The type of the value object did not match the specified RegistryValueKind or the object could not be properly converted."
如果我注释掉-replace部分,即:
$nval = $val #-replace "(?i:^(C:\\users\\Administrator))", "%USERPROFILE%"
注册表项更新时没有错误(显然具有相同的值)。
因此,-replace操作中的某些内容正在改变数据类型。它似乎改为类型System.Object []。
registryKey.SetValue方法需要字符串[]来设置REG_MULTI_SZ。
为什么要更改类型?我该如何解决这种问题呢?
更新
在答案中应用选项#1不起作用。即使在添加演员之后,$nval
仍然是System.Object[]
类型。应用选项#3确实有效。
以下是正确搜索并替换注册表中字符串开头的字符串的最终代码。
$keys = @(gci -Path hkcu:\ -recurse -ErrorAction SilentlyContinue)
foreach ($key in $keys)
{
foreach ($vname in $key.GetValueNames())
{
$val = $key.GetValue($vname, $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
if ($val -like "c:\users\administrator*")
{
if ($key.GetValueKind($vname) -eq [Microsoft.Win32.RegistryValueKind]::MultiString)
{ $nval = $val -replace "(?i:^(C:\\users\\Administrator))", "%USERPROFILE%" -as [string[]] }
else
{ $nval = $val -replace "(?i:^(C:\\users\\Administrator))", "%USERPROFILE%" }
write-host "$key\$vname=$val -> $nval"
((Get-Item $Key.PSParentPath).OpenSubKey($Key.PSChildName, "True")).SetValue($vname, $nval, $key.GetValueKind($vname))
}
}
}
答案 0 :(得分:4)
PowerShell中返回多个对象的大多数内容都将它们返回为[object[]]
;很可能是因为你可以返回任意数量的不同对象,并且因为PowerShell包装[PSObject]
中的大多数对象,无论你是否知道它。
因此,虽然您可以从[string[]]
开始,但修改它会产生[object[]]
。
当你需要一个特定类型的数组时,简单的方法就是抛出它。
您可以使用3个选项进行投射:转换赋值或转换变量,或使用-as
运算符。
$nval = [string[]]($val -replace "(?i:^(C:\\users\\Administrator))", "%USERPROFILE%")
现在$nval
将是[string[]]
,除非您稍后再为其分配其他内容。
[string[]]$nval = $val -replace "(?i:^(C:\\users\\Administrator))", "%USERPROFILE%"
这有点不同。将强制转换放在PowerShell中的变量上会将该强制转换应用于分配给它的所有值。
如果您知道$nval
始终需要[string[]]
并且您不想在每项作业中投放它,这是一个不错的选择。
-As
运营商:$nval = $val -replace "(?i:^(C:\\users\\Administrator))", "%USERPROFILE%" -as [string[]]
这类似于投射价值;不同之处在于,如果演员表不成功,它不会抛出异常,只会返回$null
。
为了处理不同的注册表种类和所需的演员表,我在评论中推荐了switch
语句,但我想出了一个更好的主意:只需使用哈希表:
$kindToType = @{
[Microsoft.Win32.RegistryValueKind]::MultiString = [string[]]
[Microsoft.Win32.RegistryValueKind]::ExpandString = [string]
[Microsoft.Win32.RegistryValueKind]::String = [string]
[Microsoft.Win32.RegistryValueKind]::DWord = [int]
# etc.
}
然后:
$nval = $val -replace "(?i:^(C:\\users\\Administrator))", "%USERPROFILE%" -as $kindToType[$key.GetValueKind($vname)]
这将创建一个查找表,您可以在其中直接从注册表类型中获取类型。
答案 1 :(得分:2)
使用重点摘要补充briantist's helpful answer:
如果-replace
表达式的LHS是集合 [1]
,替换是在的每个元素上进行的。
结果以常规PowerShell数组的形式返回,其类型为[System.Object[]]
- ,与输入集合的具体类型无关。
要保留输入集合类型,您必须使用显式强制转换或调用适当的构造函数 - 请参阅briantist的答案。
示例:
# Apply -replace to a LHS of type [System.Collections.ArrayList]
$result = ([System.Collections.ArrayList] ('one', 'two')) -replace 'o', '@'
> $result
@ne
tw@
> $result.GetType().FullName # inspect the result's type
System.Object[]
[1]没有查看源代码,似乎"集合"在这个意义上,指任何实现接口[System.Collections.IEnumerable]
的类型,它不会也实现[System.Collections.IDictionary]
;对于给定的实例$coll
,您可以按如下方式进行测试:
$coll -is [System.Collections.IEnumerable] -and $coll -isnot [System.Collections.IDictionary]
。
值得注意的是,此排除类似哈希表的集合,例如[hashtable]
(文字语法@{ ... }
)和[System.Collections.Specialized.OrderedDictionary]
(文字语法[ordered] @{ ... }
)。