- Export and import to and from Excel with the PowerShell module ImportExcel - Thu, Apr 21 2022
- Getting started with the PSReadLine module for PowerShell - Thu, Feb 24 2022
- SecretsManagement module for PowerShell: Save passwords in PowerShell - Tue, Dec 22 2020
In my example, we're going to create a tool that displays the most common fields needed to help employees who are calling into the helpdesk for assistance and display that info in a simple, easy-to-read output. I am going to walk you through building this script piece by piece and then present the final solution at the end of this article.
My goal is make it simple to display all the relevant the Active Directory fields related to user account status, account expiration, password status, as well as when they last set their password and when it will expire. I would also like to know if the account is locked out. Finally, I also need to display the common employee info fields (name, address, department, manager, title, etc.). The tool also needs to be simple for my helpdesk staff to use since they sometimes deal with a large volume of calls.
Of course, you can just run Get-ADUser to retrieve information about Active Directory users. However, you usually can't expect helpdesk staff to be typing in super-long commands like the one below to get the info they need.
get-aduser username -Properties * | Select-Object GivenName, Surname, SamAccountName, Manager, DisplayName, ` City, EmailAddress, EmployeeID, Enabled, Department, OfficePhone, MobilePhone, LockedOut, LockOutTime, AccountExpirationDate, ` PasswordExpired, PasswordLastSet, Title
The block of code above produces the following output:
This syntax works fine and gets us most of what we need without a ton of work. Overall, this is close to what I am looking to achieve. We could stop right here and call it a day, and for many people this would a usable solution. However, it isn't the most elegant solution, and more importantly, the output above is missing the password-expiration date, and the manager field is not easily readable. I think we can do better.
I certainly wouldn't expect someone to type in all that data over and over. We're close, but we must make it simple to type. Also, when I look at these fields, I see two sets of data: employee information and account status information. But the fields are not organized in any way, and the output is just a long table.
Let's address the two problem fields first. The manager field is not formatted as I would prefer: I would like to see first name and last name. We can fix this by doing a lookup on the manager's name and returning the manager's SAMAccountName. I used SAMAccountName because the DisplayName is listed as Last name, First name. This is not what I wanted, but you may like this field better for your version of this script.
I am using Get-ADUser a second time to look up the SamAccountName of the manager and then I am saving that value to a variable named $Manager.
$Manager = ((Get-ADUser $Employee.manager).samaccountname)
The password expiration is tricky because it's a special field that isn't easy to find if you don't know what you’re looking for and the value is not in a human-readable form by default. I'll need to make two changes to get the desired result. The first thing I need to do is add the field "msDS-UserPasswordExpiryTimeComputed" to my original Get-ADuser query, and then I need to convert the data returned to a format we can make sense of.
Here I convert the msDs-UserPasswordExpiryTimeComputed value to a readable date value and save the output to a variable named $PasswordExpiry
$PasswordExpiry = [datetime]::FromFileTime($Employee.'msDS-UserPasswordExpiryTimeComputed')
Now that I have all the fields I need, I can work on breaking up the original output into two distinct groups (employee information and account status information), but using Select-Object to display the fields probably isn’t going to give me the desired result. I could feed that data from Get-ADUser into two separate arrays, and then I could display each array separately. Using arrays lets me rename fields in Active Directory like "given name" and "surname" to more commonly used names like FirstName and LastName. Let's see what those arrays look like.
$AccountInfo = [PSCustomObject]@{ FirstName = $Employee.GivenName LastName = $Employee.Surname Title = $Employee.Title Department = $Employee.Department Manager = $Manager City = $Employee.City EmployeeID = $Employee.EmployeeID UserName = $Employee.SamAccountName DisplayNme = $Employee.Displayname EmailAddress = $Employee.Emailaddress OfficePhone = $Employee.Officephone MobilePhone = $Employee.Mobilephone } $AccountStatus = [PSCustomObject]@{ PasswordExpired = $Employee.PasswordExpired AccountLockedOut = $Employee.LockedOut LockOutTime = $Employee.LockOutTime AccountEnabled = $Employee.Enabled AccountExpirationDate = $Employee.AccountExpirationDate PasswordLastSet = $Employee.PasswordLastSet PasswordExpireDate = $PasswordExpiry }
The result is we have taken the information from our original lookup and have split the data into two smaller groups. Those groups now contain field names that are more user friendly as well. You could argue that this isn't necessary, and you would not be wrong.
The reason for doing this extra work is to get the output organized in the way I want it displayed with the field names I want. I could instead have run Get-ADuser twice in one script. The first Get-ADUser could have got just the account info fields, and then I could've run Get-ADuser again to lookup the Account status fields.
However, those two calls still would not convert the msDs-UserPasswordExpiryTimeComputed value to a readable date value, so I would still need to do more work afterwards. For me, arrays offer more flexibility with formatting and naming and allow me to add other fields that weren't available with the original Get-ADuser query.
Also, I believe that it's not enough just to build a tool that gives me the desired result. Instead, the tool should also be well documented, easy to follow, and built using best practices. Arrays are a better solution to the problem, and my code will be easier to understand.
Lastly, I'd like to remind you that the fields I chose may not be the ones you want to use. It's only a small amount of work to customize this as you wish. Let's see what the output of the two arrays looks like:
We have broken up the output into two smaller groups. I think that output is better organized and easier to read at a glance, which was my main concern when starting this project. The result is that helpdesk staff members will have an easier time finding the info they need.
I prefer to run my tools as functions. This means we need to name our function and include help files so people have something to reference when they have questions. The script below is formatted as a function, which means you'll have to load the function to run it. I'll assume you already know how to do that. I named my function Get-EmployeeInfo. The syntax is simple to type:
The result is that we have a tool that is easy to use, with a simple syntax that results in a customized output that is easy to read. I also created code that follows best practices, is succinct, portable, and well documented.
My hope is that you found this article insightful, and it helps you build your own functions using customized lookups. Please leave me some feedback in the comments section so I can continue to write useful articles. If there is something you would love to see me write about or if you have a specific question about this or any or my other articles, please feel free to leave me a note as well.
Subscribe to 4sysops newsletter!
Function Get-EmployeeInfo { <# .Synopsis Returns a customized list of Active Directory account information for a single user .Description Returns a customized list of Active Directory account information for a single user. The customized list is a combination of the fields that are most commonly needed to review when an employee calls the helpdesk for assistance. .Example Get-EmployeeInfo Joe_Smith Returns a customized list of AD account information fro Michael_Kanakos PS C:\Scripts> Get-EmployeeInfo Joe_Smith FirstName : Joe LastName : Smith Title : Marketing Analysyt Department : Marketing Manager : Tom_Jones City : New York EmployeeID : 123456789 UserName : Joe_Smith DisplayNme : Smith, Joe EmailAddress : Joe_smith@bigfrom.biz OfficePhone : +1 631-555-1212 MobilePhone : +1 631-333-4444 PasswordExpired : False AccountLockedOut : False LockOutTime : 0 AccountEnabled : True AccountExpirationDate : PasswordLastSet : 3/26/2018 9:29:02 AM PasswordExpireDate : 9/28/2018 9:29:02 AM .Parameter UserName The employee account to lookup in Active Directory .Notes NAME: Get-EmployeeInfo AUTHOR: Mike Kanakos LASTEDIT: 2018-04-14 .Link https://github.com/compwiz32/PowerShell #> [CmdletBinding()] Param( [Parameter(Mandatory = $True, Position = 1)] [string]$UserName ) #Import AD Module Import-Module ActiveDirectory $Employee = Get-ADuser $UserName -Properties *, 'msDS-UserPasswordExpiryTimeComputed' $Manager = ((Get-ADUser $Employee.manager).samaccountname) $PasswordExpiry = [datetime]::FromFileTime($Employee.'msDS-UserPasswordExpiryTimeComputed') $AccountInfo = [PSCustomObject]@{ FirstName = $Employee.GivenName LastName = $Employee.Surname Title = $Employee.Title Department = $Employee.department Manager = $Manager City = $Employee.city EmployeeID = $Employee.EmployeeID UserName = $Employee.SamAccountName DisplayNme = $Employee.displayname EmailAddress = $Employee.emailaddress OfficePhone = $Employee.officephone MobilePhone = $Employee.mobilephone } $AccountStatus = [PSCustomObject]@{ PasswordExpired = $Employee.PasswordExpired AccountLockedOut = $Employee.LockedOut LockOutTime = $Employee.AccountLockoutTime AccountEnabled = $Employee.Enabled AccountExpirationDate = $Employee.AccountExpirationDate PasswordLastSet = $Employee.PasswordLastSet PasswordExpireDate = $PasswordExpiry } $AccountInfo $AccountStatus } #END OF FUNCTION
One thing this doesn’t account for is a user who has ‘password never expires’. The [datetime]::FromFileTime code throws an error.
Hi Pat,
That a valid point and an oversight on my part when I was designing this script. Last month, I posted a password expiration reminder script and accounted for that scenario. We could take that portion of code and implement here very easily. The code to do that check would be something like this:
Get-ADUser -filter {Enabled -eq $True -and PasswordNeverExpires -eq $False}
I can go back and edit this script later today to account for your scenario. Thanks for pointing that out.
What I did was just throw the $PasswordExpiry line into a Try{} block, such as:
try {$PasswordExpiry = [datetime]::FromFileTime($Employee.’msDS-UserPasswordExpiryTimeComputed’)}
catch{}
And that seemed to work fine.
I also changed it to be a single object for the output.
I think in hindsight it would have been smarter for me to check for a non-expiring password and alert the user doing the check to that condition.
“Password never expires” is not really a problem per se, instead, the issue is that it causes an error in the original version script. It could be a valid config for certain accounts and, as such, probably should be surfaced to the person running the script in some way.
Thanks Mike for summarizing this. I think I can use this to refine my user search PS scrapbook. Just to add I use my select parameters in an array that I call into the command as bellow.
In fact I put all my select arrays to a file and call them in the commands.
Interesting idea. I never thought of doing that way. I’ll have to try that out soon.
Hello I have this error and I know now why?
Get-ADUser : Cannot validate argument on parameter ‘Identity’. The argument is null or an element of the argument collection contains a null value.
At C:\Get-EmployeeInfo.ps1:63 char:29
+ $Manager = ((Get-ADUser $Employee.manager).samaccountname)
+ ~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [Get-ADUser], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.ActiveDirectory.Management.Commands.GetADUser
Hello Armando,
The error message means that you did not enter a username to lookup. Why don’t you paste your syntax here and I’ll point you in the right direction.
A valid syntax would be:
Get-EmployeeInfo username
replace username with the actual username you want to look up.
Getting my feet wet with PowerShell and this is a great start. Keep it coming.. Thanks Mike.
Thanks O’neil! Glad I can be of some value to you. I ‘ve got plenty more to share!
Send it all over. I’m willing to learn from the pros…
Hello
Try below, it worked
$Manager = ((Get-ADUser $Employee | select-object -ExpandProperty manager).samaccountname)
Thanks
Hi Mike, i use this sintax: Get-EmployeeInfo username and replace username for myuser, for example:
Get-EmployeeInfo armando and I get:
PS C:> Get-EmployeeInfo armando
Get-ADUser : Cannot validate argument on parameter ‘Identity’. The argument is null or an element of the argument collection contains a null value.
At C:\Users\adm_es00594132\Documents\Get-EmployeeInfo.ps1:61 char:29
+ $Manager = ((Get-ADUser $Employee.manager).samaccountname)
+ ~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [Get-ADUser], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.ActiveDirectory.Management.Commands.GetADUser
Where i need to put or define the username??
Hi Mike, in think i got it, the problem is the username, my username is es00594132,
why I can´t put es00594132 in a variable? for example:
PS C:\> $username = es00594132
es00594132 : The term ‘es00594132’ is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that
the path is correct and try again.
At line:1 char:13
+ $username = es00594132
+ ~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (es00594132:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
Thank you very much.
Hi Armando,
I think you already figured out that ‘Armando’ is not your username, so Get-ADUser throws an error.
Powershell thinks you are typing a string of text but text need to be in enclosed with single or double quotes for it to be recognized as a string value. For example, I get the same error as you if I type the same thing:
The bold text tells me Powershell thinks the data is a string value, so I should have used quotes.
the value is accepted without issue.
Let’s go back to the first issue. Please don’t take this the wrong way, if you know this already, I apologize… but this is a function, not a script. you can’t just run the code and make it work. You have to load it into memory first.
Did you load the function? After loading the function (called dot sourcing), then you can run the function.
Please write back and I will attempt to help more or send me an email at mkanakos@gmail.com and I would be glad to try and help.
Here’s a tweaked version that deals with ‘never expire’, as well as outputting a single object, and incorporates support for pipeline input. Also, the URL in your comment block is incorrect.
HELP!!
I am pulling my hair and nothing seems to be helpful. I am trying to get below information for all Active users in AD, can you PLEASE help??
SamAccountName, AccountExpirationDate, Department, Givenname, Surname, EmailAddress, title, Manager, EmployeeType, LastLogonDate
Hi Moin,
Are u asking for a simple output of all employees and the output should have all the fields you mentioned listed?
Hi Mike,
Yes, simple output exported to .txt or .csv format with all the fields in it.
Here you go!
another way of doing the same thing that is a little easier to read would
Tried first one, works great! however, give below error at finish;
Get-Aduser : Not a valid Win32 FileTime.
Parameter name: fileTime
At line:3 char:1
+ Get-Aduser -filter "enabled -eq '$true'" -property $props | Select-Ob …
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Get-ADUser], ArgumentOutOfRangeException
+ FullyQualifiedErrorId : Not a valid Win32 FileTime.
Parameter name: fileTime,Microsoft.ActiveDirectory.Management.Commands.GetADUser
Moin,
I ran both without issue. I did see that there were some backslash’s missing from the Export-CSV cmdlet. I fixed that above. Re-copy code and re-test.
tried again, worked! Thank you so much
Can we also get below information in addition?
Date of user account creation
Is the user account active or inactive
Password expiry date
Hi Mike,
I was able to figure out about account creation date.
Only thing now I am pending is Password expiry Date, can you please help me incorporate this with the existing script?
Hi Mike,
I need to do get the same kind of output but with the list of users (not for all employees). I would be having the list in .txt file or csv and the output should be properties of Users in csv or .txt files.
Hi Gokul,
The tool in this article does not accept pipeline input because i did not include the parameter to do so. This required to be able to pass a list to the function. Take a look at my website https://www.commandline.ninja . I'll write up a newer version of this tool on my site this week.
Thank you for the fast response Mike. Waiting for your newer script for fetching the User properties for a list of users.
For your information, I am using below script right now for a list users but one by one which is real pain.
get-aduser USERNAME -Properties * | Select-Object canonicalName, cn, company, description, displayName, employeeID, extensionAttribute1, extensionAttribute4, extensionAttribute11, extensionAttribute12, homeDirectory, mailNickname, sAMAccountName, userPrincipalName, profilePath
Hi Mike,
I figured out about the account creation date and the script you provided earlier gives only active accounts.
All now I need is to get Password Expiry date incorporated with the same script.
If you can please assist.
How do I make this ask for a specific username/name first so that way it'll only show the entered ID's information
Hi Dexter…. Let me see if I can answer your question…
Are you asking… How do I run this function for one user ?
Are you asking… How can I make the function prompt for a username to enter?
The default parameter of the function is username and it is a required parameter. If you run the function with no input, it will require you to enter a username…
Take a look at the link below… I ran the function with no input and it prompts me for a username.
Screencap
Hi there! Is it possible to get rid of the [PSCustomObject] thing for the $AccountInfo variable?
I can't run this function as normal user because I get an error / runtime exception
+ $AccountInfo = [PSCustomObject]@{
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [], RuntimeException
+ FullyQualifiedErrorId : ConversionSupportedOnlyToCoreTypes
Turil,
I believe your getting this error because your running a constrained language mode session. I may be wrong but this article is a useful for troubleshooting
https://community.spiceworks.com/topic/1451109-srp-whitelist-causing-odd-behavior-in-powershell-v5
Yes, Active Directory policy enforces that on our clients unfortunately. Do you have any pointers how I could rewrite the script to work with those constraints? In the end all I want is to read some data …
Hello Mike,
I want to develop an application for an organization's internal use. For that, I required all employee data and I want to store that in my MS SQL database. So, how I can fetch all employee's data from active directory and store it in the database.
Thanks in advance…!
I am sorry Vishal. I am not sure how to make a connection to a SQL database.
Again your question is very broad. We dont know what is your SQL structure, what you need etc.
To push something to the SQL database via Powershell you can use Invoke-SQLcmd commandlet.
Thanks, Mike for the reply.
Can you help me to fetch all employee's data from active directory and store data somewhere on my system, like a text file or excel file?
Vishal,
what exactly do you need? Your question is a bit unspecified. To simply achieve the export you can modify the function end.
Vishal,
There are plenty of article (probably some on this site) that discuss connecting to SQL. Connecting to a SQL instance is also not hard but outside the scope of the original article and not something I am going to comment on here.
Saving the data to a file is an easy task. Look at export-csv or out-file cmdlets. Leos' examples are exactly correct. if you need help with saving data, then you need to be more specific. However, we are far from a relevant discussion that relates to this article.
HI,
Any idea how to get info on the current status of " Deny the user permissions to log on to Remote Desktop Session Host server" AD user attribute for all AD users.
Hi ,
Need help to get the current status of " Deny the user permissions to log on to Remote Desktop Session Host server" which comes under "Remote Desktop services profile" in AD user properties.