PowerShell: Calculated Properties on User Objects
Posted on: February 23rd, 2025
Chances are, if you've let your colleagues or bosses know you have a thing for PowerShell, you may find yourself subject to frequent requests for reports. You may be asked to report values that don't exist on the objects you're querying. However, that doesn't mean they are impossible to calculate based on what you do have available to you. In this article you'll read about Calculated Properties: how to make them and some ways we can use them to advance our reporting abilities.
What Are Calculated Properties?
All objects have attributes. Active Directory users have a UserPrincipalName, a DistinguishedName, a SamAccountName. Computer objects have a CN, OperatingSystem, lastLogon. Service principals in Entra ID all have a DisplayName, an app and object ID, and a SignInAudience. All of these attributes can be used to create new, temporary attributes to be added to your query outputs. They can be used with each other, math equations, other cmdlets and variables, etc. These calculated properties are not applied to the objects they are associated with. They are generated on the fly and instantly disposed of. They are not stored anywhere unless you export your output or capture it in a variable.
Creating a Calculated Property
If you're familiar with building hash tables, you'll notice this is pretty similar. If not, don't fret. I found memorizing the formatting to be harder than getting them to work. You'll need to start with a supported cmdlet. We will focus on Select-Object for this article, but several others can be used.
For a quick and easy example to demonstrate a simple calculated property, we'll combine a user's Surname attribute with their GivenName attribute to create 'LastName, FirstName'. You do not necessarily need to select the attributes you are using to calculate a property, but we will do so here to better present the example. First, set up your query with Get-AdUser (a single user for sake of simplicity), select the GivenName and Surname, and then build your calculated property. You'll want to enclose it all in a hash table, @{}, and declare a name and an expression. The name will be the "attribute" name, and the expression will be how we formulate the property. Enclose your expression in brackets, {}.
### Use Get-AdUser to get Tony's user object, calculate a "FullName" property from Surname and GivenName
Get-ADUser -Identity tony | `
select GivenName,Surname,@{
Name='FullName';
Expression={
$_.Surname + ', ' + $_.GivenName
}
}
### Expected Output
#GivenName Surname FullName
#--------- ------- --------
#Tony Danza Danza, Tony
Name is interchangeable with Label, and each can be short-handed with their first letter. n for Name or l for Label, and e for Expression. You can argue that not using aliases and short-hand is better for documentation, but it's just so convenient. I'll switch to the short-hand moving forward.
Calculating Values
We've seen a simple example of a calculated property only using two existing attributes. As mentioned before, calculated properties can be in math equations and other cmdlets. For our next example, we'll calculate several new properties all at once. These will showcase using two existing attributes together, using a single existing attribute and simply giving it a different name in your output, taking an existing attribute and reformatting it, using math and/or other cmdlets to calculate a value, and including if-else statements to declare a value based on a condition. You will see that you can essentially have expressions that are scripts of their own.
Be wary of the number of objects you will be querying and how large your query is. Each expression, particularly as they become longer or more complex, can drastically increase the time it takes to complete your query. Make an effort to keep things short and efficient; if the expression CAN be done on one line, it SHOULD be done on one line. The following example is not meant to be incredibly efficient or practical. It is only an example to show what is possible. Check the comments within the scriptblock for explanations.
### Get-AdUser to get Tony's user object, calculate various properties for output
Get-ADUser -Identity tony -Properties proxyAddresses,whenCreated,pwdLastSet | `
select @{
### Combine Surname and GivenName, seperate with a comma
n='FullName';
e={
$_.Surname + ', ' + $_.GivenName
}
},@{
### Use proxyAddresses and limit to main SMTP address, denoted with capital letter "SMTP:"
### Remove "SMTP:" for output
n='MainSmtp';
e={
($_.proxyAddresses -cmatch 'SMTP:') -replace 'SMTP:',''
}
},@{
### Use whenCreated on its own to output the same value under a different name
n='DateCreated';
e={
$_.whenCreated
}
},@{
### AD outputs pwdLastSet in Windows NT 18-character format
### Reformat to a human-readable date/time
n='pwdLastSetDate';
e={
[DateTime]::FromFileTime($_.pwdLastSet)
}
},@{
### Get age of password in number of days. Use pwdLastSet and reformat, subtract from current
### date/time retrieved from Get-Date. Output value as 'Days'
n='pwdAge';
e={
((Get-Date) - [DateTime]::FromFileTime($_.pwdLastSet)).Days
}
},@{
### (Use Connect-MgGraph before trying) Set variable with UPN for Get-MgUser cmdlet
### Query for UserId
n='EntraUserId';
e={
$upn = $_.UserPrincipalName
(Get-MgUser -Search "UserPrincipalName:$upn" -ConsistencyLevel eventual).Id
}
},@{
### Query AdminAccounts for member SID values. If SID of queried user(s) found
### in group's list of SIDs, value is 'Yes'. If not found, value is 'No'
n='IsAdmin';
e={
$adminSids = (Get-ADGroupMember -Identity AdminAccounts).Sid
if ($adminSids -contains $_.Sid) {
'Yes'
} else {
"No"
}
}
}
### Expected Output
#
#FullName : Danza, Tony
#MainSmtp : [email protected]
#DateCreated : 1/4/2024 10:05:09 AM
#pwdLastSetDate : 2/4/2025 12:34:31 PM
#pwdAge : 19
#EntraUserId : 12345678-1234-4321-1234-1234567890ab
#IsAdmin : No
Conclusion
Calculated properties will take your reporting to the next level. You can create values that didn't exist before, which can greatly close the gap on what is possible to be pulled natively from Windows, Entra ID, Active Directory, etc. and what may be asked of you when information is needed. Just make sure to let those doing the requesting know that, depending on the size of your environment or query pool along with how much calculating needs done, these queries may take some time to complete. Which brings me to some important advice, do your testing with small samples. You don't want a 6-hour query on tens of thousands of objects to complete just for you to notice one of your expressions has a misspelled attribute in it. Add something like '-First 10' or '-Last 25' to the end of your select statement, etc.
Do you have anything to add? Did you find this particularly helpful to you? Tell me about it! Reach out to me at [email protected] or on LinkedIn. I am always happy to discuss automation, identity, server administration, technology in general.