ShouldProcess 在 PowerShell7 中失败
ShouldProcess failing in PowerShell7
环境:Windows Server 2022 21H2,Powershell 7.2(运行 作为管理员)
我有一个实现 ShouldProcess 的脚本,它在 Windows PowerShell 5 中工作正常。但是,在 PowerShell 7 中,脚本总是抛出错误 Cannot find an overload for "ShouldProcess" and the argument count: "1".
ShouldProcess at MSDoc 说$PSCmdlet.ShouldProcess()
的单参数重载存在并且应该有效。
如上,失败。为什么?
有问题的脚本粘贴在下面;它在脚本模块中:
function Remove-DomainUserProfile {
<#
#Comment-based help removed for space considerations
#>
[CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact="High")]
param(
[Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[Parameter(ParameterSetName='SpecificProfile')]
[Parameter(ParameterSetName='ByAge')]
[Parameter(ParameterSetName='AllProfiles')]
[String[]]$ComputerName = $env:ComputerName,
[Parameter(Mandatory=$true,ParameterSetName='SpecificProfile')]
[Parameter(ParameterSetName='ByAge')]
[Alias("UserName","sAMAccountName")]
[String]$Identity,
[Parameter(ParameterSetName='ByAge')]
[Parameter(ParameterSetName='AllProfiles')]
[Switch]$DomainOnly,
[Parameter(ParameterSetName='SpecificProfile')]
[Parameter(ParameterSetName='ByAge')]
[Int]$Age,
[Parameter(Mandatory=$true,ParameterSetName='AllProfiles')]
[Switch]$All
)
BEGIN {
if (-NOT (Test-IsAdmin)) {
Write-Output "This function requires being run in an Administrator session! Please start a PowerShell
session with Run As Administrator and try running this command again."
return
}
$NoSystemAccounts = "SID!='S-1-5-18' AND SID!='S-1-5-19' AND SID!='S-1-5-20' AND NOT SID LIKE 'S-1-5-%-500' "
# Don't even bother with the system or administrator accounts.
if ($DomainOnly) {
$SIDQuery = "SID LIKE '$((Get-ADDomain).DomainSID)%' " # All domain account SIDs begin
with the domain SID
} elseif ($Identity.Length -ne 0) {
$SIDQuery = "SID LIKE '$(Get-UserSID -AccountName $Identity)' "
}
$CutoffDate = (Get-Date).AddDays(-$Age)
$Query = "SELECT * FROM Win32_UserProfile "
}
PROCESS{
ForEach ($Computer in $ComputerName) {
Write-Verbose "Processing Computer $Computer..."
if ($SIDQuery) {
$Query += "WHERE " + $SIDQuery
} else {
$Query += "WHERE " + $NoSystemAccounts
}
if ($All) {
Write-Verbose "Querying WMI using '$Query'"
$UserProfiles = Get-WMIObject -ComputerName $Computer -Query $Query
} else {
Write-Verbose "Querying WMI using '$Query' and filtering for profiles last used before $CutoffDate ..."
$UserProfiles = Get-WMIObject -ComputerName $Computer -Query $Query | Where-Object {
[Management.ManagementDateTimeConverter]::ToDateTime($_.LastUseTime) -lt $CutoffDate }
}
ForEach ($UserProfile in $UserProfiles) {
if ($PSCmdlet.ShouldProcess($UserProfile)) {
Write-Verbose "Deleting profile object $UserProfile ($(Get-SIDUser $UserProfile.SID))..."
$UserProfile.Delete()
}
}
}
}
END {}
}
作为参考,此错误可以在 PowerShell 5.1 版和 Core 上重现。重现的步骤是将 System.Management.Automation.PSObject
作为参数传递给 .ShouldProcess(String)
重载。通过查看您提到 序列化对象 的评论,这是有道理的。在下面的示例中,如果 System.Diagnostics.Process
对象 未序列化 它在两个版本上都能正常工作。
function Test-ShouldProcess {
[CmdletBinding(SupportsShouldProcess, ConfirmImpact = "High")]
param()
$obj = [System.Management.Automation.PSSerializer]::Deserialize(
[System.Management.Automation.PSSerializer]::Serialize((Get-Process)[0])
)
# will throw
if ($PSCmdlet.ShouldProcess($obj)) { 'hello' }
}
Test-ShouldProcess
补充:
该行为至少出现在 PowerShell 7.2.1 中,应被视为 bug,因为任何 对象都应auto-convertible 为.NET 方法调用中的字符串。
没有理由 [pscustomobject]
a.k.a [psobject]
实例的行为不同于任何其他类型的实例(无论隐式字符串化在给定情况);举个简单的例子:
- 如果
(42).ToString((Get-Item /))
有效,...
- ...没有理由
(42).ToString(([pscustomobject] @{ foo=1 }))
不应该。
- 请注意,cmdlet/函数/脚本上下文中的隐式字符串化不受影响;例如,
Get-Date -Format ([pscustomobject] @{ foo=1 })
不会导致错误。
-
涉及序列化基础结构的原因根本是 过时的 WMI cmdlet,例如 Get-WmiObject
aren't natively available in PowerShell (Core) v6+ anymore, and using them implicitly makes use of the Windows PowerShell Compatibility 特征:
这需要使用一个隐藏的 powershell.exe
子进程,与之通信需要使用序列化,在此期间大多数 non-primitive 类型失去了它们的类型标识并且是 用 method-less [psobject]
个包含原始对象属性副本的实例模拟。
在 PowerShell v3 及更高版本中,尤其是在 PowerShell (Core) v6+ 中,请改用 CIM cmdlet ,例如Get-CimInstance
,改为:
- 虽然在许多方面类似于 WMI cmdlet,但一个重要的区别是从 CIM cmdlet 返回的对象没有方法;相反,必须通过
Invoke-CimMethod
. 调用 方法
有关详细信息,请参阅 this answer。
环境:Windows Server 2022 21H2,Powershell 7.2(运行 作为管理员)
我有一个实现 ShouldProcess 的脚本,它在 Windows PowerShell 5 中工作正常。但是,在 PowerShell 7 中,脚本总是抛出错误 Cannot find an overload for "ShouldProcess" and the argument count: "1".
ShouldProcess at MSDoc 说$PSCmdlet.ShouldProcess()
的单参数重载存在并且应该有效。
如上,失败。为什么?
有问题的脚本粘贴在下面;它在脚本模块中:
function Remove-DomainUserProfile {
<#
#Comment-based help removed for space considerations
#>
[CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact="High")]
param(
[Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[Parameter(ParameterSetName='SpecificProfile')]
[Parameter(ParameterSetName='ByAge')]
[Parameter(ParameterSetName='AllProfiles')]
[String[]]$ComputerName = $env:ComputerName,
[Parameter(Mandatory=$true,ParameterSetName='SpecificProfile')]
[Parameter(ParameterSetName='ByAge')]
[Alias("UserName","sAMAccountName")]
[String]$Identity,
[Parameter(ParameterSetName='ByAge')]
[Parameter(ParameterSetName='AllProfiles')]
[Switch]$DomainOnly,
[Parameter(ParameterSetName='SpecificProfile')]
[Parameter(ParameterSetName='ByAge')]
[Int]$Age,
[Parameter(Mandatory=$true,ParameterSetName='AllProfiles')]
[Switch]$All
)
BEGIN {
if (-NOT (Test-IsAdmin)) {
Write-Output "This function requires being run in an Administrator session! Please start a PowerShell
session with Run As Administrator and try running this command again."
return
}
$NoSystemAccounts = "SID!='S-1-5-18' AND SID!='S-1-5-19' AND SID!='S-1-5-20' AND NOT SID LIKE 'S-1-5-%-500' "
# Don't even bother with the system or administrator accounts.
if ($DomainOnly) {
$SIDQuery = "SID LIKE '$((Get-ADDomain).DomainSID)%' " # All domain account SIDs begin
with the domain SID
} elseif ($Identity.Length -ne 0) {
$SIDQuery = "SID LIKE '$(Get-UserSID -AccountName $Identity)' "
}
$CutoffDate = (Get-Date).AddDays(-$Age)
$Query = "SELECT * FROM Win32_UserProfile "
}
PROCESS{
ForEach ($Computer in $ComputerName) {
Write-Verbose "Processing Computer $Computer..."
if ($SIDQuery) {
$Query += "WHERE " + $SIDQuery
} else {
$Query += "WHERE " + $NoSystemAccounts
}
if ($All) {
Write-Verbose "Querying WMI using '$Query'"
$UserProfiles = Get-WMIObject -ComputerName $Computer -Query $Query
} else {
Write-Verbose "Querying WMI using '$Query' and filtering for profiles last used before $CutoffDate ..."
$UserProfiles = Get-WMIObject -ComputerName $Computer -Query $Query | Where-Object {
[Management.ManagementDateTimeConverter]::ToDateTime($_.LastUseTime) -lt $CutoffDate }
}
ForEach ($UserProfile in $UserProfiles) {
if ($PSCmdlet.ShouldProcess($UserProfile)) {
Write-Verbose "Deleting profile object $UserProfile ($(Get-SIDUser $UserProfile.SID))..."
$UserProfile.Delete()
}
}
}
}
END {}
}
作为参考,此错误可以在 PowerShell 5.1 版和 Core 上重现。重现的步骤是将 System.Management.Automation.PSObject
作为参数传递给 .ShouldProcess(String)
重载。通过查看您提到 序列化对象 的评论,这是有道理的。在下面的示例中,如果 System.Diagnostics.Process
对象 未序列化 它在两个版本上都能正常工作。
function Test-ShouldProcess {
[CmdletBinding(SupportsShouldProcess, ConfirmImpact = "High")]
param()
$obj = [System.Management.Automation.PSSerializer]::Deserialize(
[System.Management.Automation.PSSerializer]::Serialize((Get-Process)[0])
)
# will throw
if ($PSCmdlet.ShouldProcess($obj)) { 'hello' }
}
Test-ShouldProcess
补充
该行为至少出现在 PowerShell 7.2.1 中,应被视为 bug,因为任何 对象都应auto-convertible 为.NET 方法调用中的字符串。
没有理由
[pscustomobject]
a.k.a[psobject]
实例的行为不同于任何其他类型的实例(无论隐式字符串化在给定情况);举个简单的例子:- 如果
(42).ToString((Get-Item /))
有效,... - ...没有理由
(42).ToString(([pscustomobject] @{ foo=1 }))
不应该。 - 请注意,cmdlet/函数/脚本上下文中的隐式字符串化不受影响;例如,
Get-Date -Format ([pscustomobject] @{ foo=1 })
不会导致错误。
- 如果
涉及序列化基础结构的原因根本是 过时的 WMI cmdlet,例如
Get-WmiObject
aren't natively available in PowerShell (Core) v6+ anymore, and using them implicitly makes use of the Windows PowerShell Compatibility 特征:这需要使用一个隐藏的
powershell.exe
子进程,与之通信需要使用序列化,在此期间大多数 non-primitive 类型失去了它们的类型标识并且是 用 method-less[psobject]
个包含原始对象属性副本的实例模拟。在 PowerShell v3 及更高版本中,尤其是在 PowerShell (Core) v6+ 中,请改用 CIM cmdlet ,例如
Get-CimInstance
,改为:- 虽然在许多方面类似于 WMI cmdlet,但一个重要的区别是从 CIM cmdlet 返回的对象没有方法;相反,必须通过
Invoke-CimMethod
. 调用 方法
- 虽然在许多方面类似于 WMI cmdlet,但一个重要的区别是从 CIM cmdlet 返回的对象没有方法;相反,必须通过
有关详细信息,请参阅 this answer。