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 }) 不会导致错误。
    • 参见 GitHub issue #16988

  • 涉及序列化基础结构的原因根本是 过时的 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