Search code examples
powershellpowershell-7.0powershell-7.2

ShouldProcess failing in PowerShell7


Environment: Windows Server 2022 21H2, Powershell 7.2 (running as administrator)

I have a script that implements ShouldProcess, which works fine in Windows PowerShell 5. However, in PowerShell 7, the script invariably throws the error Cannot find an overload for "ShouldProcess" and the argument count: "1". ShouldProcess at MSDoc says that the one-argument overload for $PSCmdlet.ShouldProcess() exists and should work.

It's failing, as above. Why?

The script in question is pasted below; it's in a script module:

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 {}
}

Solution

  • For reference, this error can be reproduced on both PowerShell versions 5.1 and Core. The steps to reproduce is passing a System.Management.Automation.PSObject as argument to the .ShouldProcess(String) overload. It makes sense, by looking at your comment mentioning a serialized object. In below example, if the System.Diagnostics.Process object is not serialized it works properly on both versions.

    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