Using PowerShell to retrieve CAC Information

, ,

So today, we have a very niche PowerShell tutorial.  If you’re part of DOD or Army IT, you’ll probably recognize this as the scripting component of the FASCN project. For everyone else, I hope there’s something here you can use.


Users have received new smart cards that contain a fourth certificate to be used for Domain authentication. These certificates have a value, that while based on the EDIPI contained in existing certificates, is expanded and will only be used for this one purpose.   Several forests full of domains full of users now have UserPrincipalName (UPN) values that must be updated to take advantage of the new certificate.


PowerShell. Thanks for coming out, everyone. Have a nice night…

Actually, this is a very straightforward script if all you care about is “lines of code.”  Right now, we only wish to collect the FASCN value from each user upon login, so we need something that can run as a scheduled task in the user’s context. We also want to make sure that we don’t get the wrong values. For example, if two smart cards are inserted into a computer (e.g. maybe a technician has logged into a user’s PC to install software), we don’t want the certificate information from the technician’s card to update the account information of the user who was originally logged in.

The Code

  1. To start the script, we want to isolate the current user’s UPN, which should be their EDIPI number if you’re rocking that DoD life.  I don’t want to require the ActiveDirectory module on each user’s computer, so we’re going to get it locally with whoami /upn instead. A little bit of string manipulation later, and the @mil is gone, leaving just the number.
  2. Next, we have to isolate the certificates from the CAC.  The certificate containing the FASCN is referred to as the Authentication certificate, so we look for that in the FriendlyName value. However, we want to make sure we get a certificate that matches the right user, so we’re also going to make sure the EDIPI value exists in the Subject value. Lastly, we don’t want any expired or cached certificates getting in our way, and checking for the HasPrivateKey value guarantees that the certificate belongs to a CAC that is currently inserted in the reader.
  3. We also want to get the user’s email address for this project, so we’re going to check the same values again, except against the Signature certificate. Variables corresponding to both the Signature  and Authentication certificates are saved so we can do more work.
  4. Both the FASCN and the email address are values saved in their certificates’ Subject Alternative Name values. Unfortunately, the raw data for these values is hideous, so we need a couple of COMobjects to decode them. You can’t use the same one to decode both values, so I create two.  While I’m at it, I save the raw data to some Strings so it’s easier to get.
  5. Once you’ve used the COMObjects to decode the raw data, we want to get the strValue values from each of them. There may be some null entries hanging around, so just throw a quick FOR loop in there to only get the entries you want.
  6. Now that you have the FASCN and Email values from the certificate, you can output them to a CSV file, write them to a text file, write back to Active Directory, or maybe send it to a printer so you can wallpaper your bodega with useless information.

Now that you have an outline, here is the code that does all the work:

$myEDIPI = (whoami /upn).replace('@mil','')
$comOBJS = @()
$PIVCERT = gci Cert:\CurrentUser\My | ? {$_.Subject -like "*$($myEDIPI)*" -and $_.FriendlyName -like "Authentication -*" -and $_.HasPrivateKey -eq $true}     	 
$EMAILCERT = gci Cert:\CurrentUser\My | ? {$_.Subject -like "*$($myEDIPI)*" -and $_.FriendlyName -like "Signature -*" -and $_.HasPrivateKey -eq $true}     	 
$Extensions=$pivcert.Extensions | Where-Object {$_.Oid.FriendlyName -eq "Subject Alternative Name"}       	 
$EmailAddress=$EMAILCERT.Extensions | Where-Object {$_.Oid.FriendlyName -eq "Subject Alternative Name"}
$comOBJS += new-object -ComObject X509Enrollment.CX509ExtensionAlternativeNames       	 
$comOBJS += new-object -ComObject X509Enrollment.CX509ExtensionAlternativeNames       	 
$comObjs[0].InitializeDecode(1, $FASCNString)       	 
$FASCN = ($comOBJS[0].AlternativeNames | ?{$_.StrValue -like "*$($myEDIPI)*"}  ).strvalue
$comOBJS[1].InitializeDecode(1, $emailString)
$email = ($comOBJS[1].AlternativeNames | ?{$_.StrValue -like "*"}).strvalue
write-host "FASCN discovered: " $FASCN
write-host "Email discovered: " $email

Archiving AD Accounts with PowerShell

, , , ,


I work for a small K-12 school district. Often when teachers retire, they return as substitutes. We are also required maintain public records for seven years. For these reasons, we do not delete staff accounts immediately after their employment ends.

I had a process for archiving employees and activating old accounts, but it was time consuming. I also wanted to ensure that procedures were followed by new staff members.  A few of the steps I take when archiving an account are, setting Logon Workstation and Logon Hours, adding the account to a “Archived Users” group, removing all other groups, and, of course, disabling the account.

I knew I could automate this with PowerShell, but I am late to game and didn’t really know where to start. I have adapted scripts for my needs, but I have not done a project like this from start to finish. In addition to automating some account management, I wanted to expand some PowerShell skills.


I was lucky enough to attend Ignite 2018 and caught Tools, tips and tricks from the SysAdmin field by my friend, Harjit (@hoorge). Towards the end of the session, around 17:20, he shows Active Directory Admin Center (ADAC) and the awesome Windows PowerShell History Viewer. For those unfamiliar with this feature, every action that you do in ADAC generates PowerShell code.

Active Directory Administrative Center

Copy and paste the code into PowerShell ISE and string each step of your process together. You will end up with a script like the one below:

Set-ADUser -Identity:"CN=Mike,OU=Users,OU=ViaMonstra,DC=corp,DC=viamonstra,DC=com" -LogonWorkstations:"No-PC-for-You" -Replace:@{"logonHours"="0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0"}
Add-ADPrincipalGroupMembership -Identity:"CN=Mike,OU=Users,OU=ViaMonstra,DC=corp,DC=viamonstra,DC=com" -MemberOf:"CN=Archived Users,OU=Security Groups,OU=ViaMonstra,DC=corp,DC=viamonstra,DC=com"
Set-ADObject -Identity:"CN=Mike,OU=Users,OU=ViaMonstra,DC=corp,DC=viamonstra,DC=com" -Replace:@{'primaryGroupID'="2103"}
Remove-ADPrincipalGroupMembership -Confirm:$false -Identity:"CN=Mike,OU=Users,OU=ViaMonstra,DC=corp,DC=viamonstra,DC=com" -MemberOf:"CN=Domain Users,CN=Users,DC=corp,DC=viamonstra,DC=com"
Set-ADAccountExpiration -DateTime:"12/29/2018 00:00:00" -Identity:"CN=Mike,OU=Users,OU=ViaMonstra,DC=corp,DC=viamonstra,DC=com"

Not only do we have a starting point for a User Management script, but we are also getting a solid introduction to PowerShell and how it works. Taking this a step further, let’s look at how to replace the -Identity parameter with a variable. To accomplish this, I created two variables for the User Account, $Account and $AccountDetails. $Account asks for the user name. $AccountDetails uses Get-AdUser to gather details of the Account, specifically DistinguishedName.

$Account = Read-Host -Prompt 'Input the user name'
$AccountDetails = Get-ADUser $Account

At this point, we can now replace




This small addition greatly increases the usefulness of this script. Since Distinguished Name contains the full path, I do not need to worry about typos in OU names. Also, I can hand this script off to help desk staff to archive accounts without worrying that a step was missed. Below is the full script that I currently use when archiving accounts. You will notice that I added several more variables, $DenyHours, $ArchivedUsersGroup, and $DisableOU. I used Richard Siddaway’s blog post, to declare $DenyHours. $ArchivedUsers and $DisabledOU are variables that I declared for my group and OU for disabled accounts.

#Declare Logon Hours
[byte[]]$DenyHours = @(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)
#Declare OUs
$DisableOU = "OU=- Disabled Accounts,OU=Users,DC=ViaMonstra,DC=org"
#Declare Groups
$ArchivedUsersGroup = "CN=Archived Users,OU=Groups,DC=ViaMonstra,DC=org"
# Input Account and get Account details
$Account = Read-Host -Prompt 'Input the user name'
$AccountDetails = Get-ADUser $Account
#Disable Account
Disable-ADAccount -Identity:$AccountDetails.DistinguishedName
# Set Logon Restrictions
Set-ADUser -Identity:$AccountDetails.DistinguishedName -LogonWorkstations:"No-PC-for-You" -Replace:@{logonHours=$DenyHours
# Add Archive Flag for Email Autoreply Rules
Set-ADUser -Identity:$AccountDetails.DistinguishedName -Office:"Archive"
# Add Account to 'Archived Users' group and Set as primary
Add-ADGroupMember -Identity:$ArchivedUsersGroup -Members:$AccountDetails
Set-ADObject -Identity:$AccountDetails -Replace:@{'primaryGroupID'="ChangeToYourGroupID"}
# Set Description to Disabled Date
Set-ADUser $AccountDetails -Description "Account Disabled on $(Get-Date -format 'd')"
# Remove From all the Groups
Get-ADGroup -Filter {name -notlike "*Archived Users*"}  | Remove-ADGroupMember -Members $AccountDetails.samaccountname -Confirm:$False 
# Move Account to Disabled Users OU
Move-ADObject -Identity:$AccountDetails.DistinguishedName -TargetPath:$DisableOU

To recap, I defined a procedure to archive accounts. Using ADAC, I captured the PowerShell commands for each step. Finally, I replaced static information such as, Account Name, OU’s, and Groups with variables. I hope that this gives you some ideas for managing user accounts with PowerShell in your environment.

Update – I added a screenshot of ADAC, changed the disable account to the cleaner “Disable-ADAccount” and fixed a couple of typos.