Search code examples
powershell

Powershell changes the return datatype


When I run a compare operation on $certificateFromIssuer within Get-IssuedCertificate I get the proper output.

$certificateFromIssuer.NotAfter -lt $today

The above works as expected.

However, when $certificateFromIssuer is returned to DeleteExpiredCertificates I get the following error.

Cannot compare "@{NotAfter=8/28/2025 5:25:53 PM}" to "8/29/2024 2:56:25 PM" because the objects are not the same type or the object "@{NotAfter=8/28/2025 5:25:53 PM}" does not implement "IComparable".

Here's my full code:

function Get-IssuedCertificates {
    param (
        [Parameter(Mandatory = $true)]
        $issuer        
    )
    
    $certificates = Get-ChildItem -Path Cert:\CurrentUser\My
    $certificatesFromIssuer = $certificates | Where-Object { $_.Issuer -like "*$issuer*" }

    if ($certificatesFromIssuer) {
        return ,$certificatesFromIssuer #| Format-Table -Property Subject, Issuer, Thumbprint, NotAfter

    } else {
        Write-Output "No certificates found from issuer: $issuer"
        return @()
    }
}

function DeleteExpiredCertificates {
    param (
        [Parameter(Mandatory = $false)]
        $expiredCertificates
    )
    $today = Get-Date
    if(($expiredCertificates | Select-Object -Property NotAfter) -lt $today) {

        Write-Host "Certificate expired on: $($expiredCertificates | Select-Object -Property NotAfter)"
    }
}
DeleteExpiredCertificates -expiredCertificates $(Get-IssuedCertificates -issuer Dexter)

Notice that, in the Delete function, I have to use $expiredCertificates | Select-Object -Property NotAfter because $expiredCertificates.NotAfter seems to return empty value which is also something I'd like to understand.

I'm expecting to understand why the datatype changes when a variable is returned from a function to another.


Solution

  • The main issue with your code is that you're trying to compare an array of objects with a property named NotAfter (this is the output from Select-Object NotAfter) with a DateTime instance. You just need a loop and get rid of Select-Object, you can use dot notation to reference a property value. Also note that [datetime]::Today and Get-Date are not the same, Get-Date is [datetime]::Now.

    function DeleteExpiredCertificates {
        param (
            [Parameter(Mandatory)]
            [X509Certificate[]] $ExpiredCertificates
        )
    
        $today = [datetime]::Today
        foreach ($cert in $ExpiredCertificates) {
            if ($cert.NotAfter -lt $today) {
                Write-Host "Certificate expired on: $($cert.NotAfter)"
            }
        }
    }
    

    If you want your functions to be more PowerShell like, you can have your DeleteExpiredCertificates take values from pipeline, so something like:

    function Get-IssuedCertificates {
        [OutputType([X509Certificate])]
        param (
            [Parameter(Mandatory)]
            [string] $Issuer
        )
    
        $certificatesFromIssuer = Get-ChildItem -Path Cert:\CurrentUser\My |
            Where-Object { $_.Issuer -like "*$Issuer*" }
    
        if ($certificatesFromIssuer) {
            return $certificatesFromIssuer
        }
    
        Write-Error "No certificates found from issuer: $Issuer"
    }
    
    function DeleteExpiredCertificates {
        param (
            [Parameter(Mandatory, ValueFromPipeline)]
            [X509Certificate] $ExpiredCertificate
        )
    
        begin { $today = [datetime]::Today }
        process {
            if ($ExpiredCertificate.NotAfter -lt $today) {
                Write-Host "Certificate expired on: $($ExpiredCertificate.NotAfter)"
            }
        }
    }
    

    And then you can pipe the output from one command to the other command:

    Get-IssuedCertificates Dexter | DeleteExpiredCertificates