I don’t know how $users was generated, but if it stores the output of Get-ADUser, there is a chance it does not have all of the properties required. That will require doing another Get-ADUser with the appropriate properties.
Starting a query from scratch, you could do something like the following:
$PasswordAge = 30 # arbitrary but substitute your own domain's password policy
$Users = Get-ADUser -Filter 'Enabled -eq $true -and PasswordNeverExpires -eq $false' -Properties Mail,PasswordLastSet |
Select-Object Name,Mail,@{n='PasswordExpiration';e={$_.PasswordLastSet.AddDays($PasswordAge)}}
If you must start from an already populated $users and it does not contain password properties, you must query them again:
Of course, you can combine the two approaches. Performing a query for each user is going to take a long time if the user list is long. It may be more efficient to just query the domain again.