Hi all. I’m Michael Rendino, Senior Premier Field Engineer, based out of the Charlotte, NC campus of Microsoft! Previously, I’ve helped you with some network capture guidance (here and here), but today, I want to talk about something different. Over the last couple of years, one of the hottest tech topics has been security (as it should be). You should be eating, sleeping and breathing it. Part of your security focus should be on mitigating pass-the-hash attacks. You’ve probably heard a ton about them, but if not, venture over to http://aka.ms/pth for a wealth of helpful information. http://aka.ms/pth for a wealth of helpful information.
One great tool that we offer for FREE (yes, really…don’t be so sarcastic) it’s the Local Administrator Password Solution, or LAPS. If you don’t believe me, go here and download it. The idea behind this tool is to eliminate those instances where you have multiple computers with the same local admin account password. With LAPS, each machine will set its own random password for the built-in local administrator account (or a different account of your choosing) and populate an attribute on that computer account in Active Directory. It’s easy to deploy and works great. The challenge comes in knowing if it’s actually working. How do you know if your machines have ever set the password? Or maybe they set it once and haven’t updated it since even though it’s past the designated expiration date? It’s definitely worth monitoring to ensure that your machines are operating as expected.
Well, internally, this question was asked long ago and the creator of LAPS, Jiri Formacek, threw together a small PowerShell script to provide that capability. I have built on what he started and have implemented this script with my customers. Since my PowerShell-fu is not super strong, I got help from Sean Kearney who helped refine it and make it cleaner. Now, my customer can easily see the status of their deployment and troubleshoot those computers that are out of compliance. By default, the LAPS health report will be written to the file share you specify, but can also email you, if you choose. Simply use the -SendMessage switch and set it to $true. Make sure to edit the SMTP settings variables first.
Requirements:
- A computer to run the script. My customer uses a Windows Server 2012 R2 box, but any computer running PowerShell 3.0 or better should work.
-
The S.DS.P PowerShell module downloaded from https://gallery.technet.microsoft.com/scriptcenter/Using-SystemDirectoryServic-0adf7ef5 and installed on that computer. If your server has internet connectivity, you can also launch PowerShell as Administrator and run “Install-Module S.DS.P“. This requires NuGet 2.8.5.201 so if it isn’t already installed, you will get prompted if you want it done.
- The script will need to be run using credentials with rights to read the LAPS attributes on the computer objects.
Once you have met those basic requirements and have adjusted the variables for your environment, run this script and get a simple report like this:
Now you can start investigating why these computers are out of compliance.
If you have deployed LAPS, I hope you find this script to be beneficial and can ensure that everything is working as expected. Good luck!
Usage
- First, where noted, edit the variables so they reflect your environment.
-
If you just run the script as-is, no email will be sent. If you want to send one, append SendMessage
$true
param(
[Switch]$SendMessage=$False
)
<#
.DISCLAIMER
This Sample Code is provided for the purpose of illustration only and is not intended to be used in a production environment. THIS SAMPLE CODE AND ANY RELATED INFORMATION ARE PROVIDED “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. We grant You a nonexclusive, royalty-free right to use and modify the Sample Code and to reproduce and distribute the object code form of the Sample Code, provided that You agree: (i) to not use Our name, logo, or trademarks to market Your software product in which the Sample Code is embedded; (ii) to include a valid copyright notice on Your software product in which the Sample Code is embedded; and (iii) to indemnify, hold harmless, and defend Us and Our suppliers from and against any claims or lawsuits, including attorneys’ fees, that arise or result from the use or distribution of the Sample Code.
#>
<#Make sure you have installed the S.DS.P module from https://gallery.technet.microsoft.com/scriptcenter/Using-SystemDirectoryServic-0adf7ef5 on the server where you will be running the script.
Thanks to Jiri Formacek for creating the foundation of the script. I just put the cherry on top!
Optimizations tweaked by Sean Kearney, Platforms PFE and ‘Scripting Guy’
#>
#Import the S.DS.P PowerShell module
Import-Module S.DS.P
#Edit the following variable to specify the domain or OU to search (e.g. workstations or servers)
$searchBase=“dc=contoso,dc=com”
#Edit the following variable to specify the LDAP server to use. Using domain name will select any DC in the domain
$Server=“contoso.com”
#Edit the following variable to specify the share to store the output.
$fileshare=“c:temp”
#Edit the following variable, if necessary, to match the LAPS group policy for the age of passwords (default is 30)
$maxAgeDays=30
$ts=[DateTime]::Now.AddDays(0–$maxAgeDays).ToFileTimeUtc().ToString()
#LDAP queries for LAPS statistics
$enrolledComputers=@(Find-LdapObject -LdapConnection $Server -searchFilter “(&(objectClass=computer)(ms-MCS-AdmPwdExpirationTime=*))” -searchBase $searchBase -PropertiesToLoad @(‘canonicalname’,‘lastlogontimestamp’))
$nonEnrolledComputers=@(Find-LdapObject -LdapConnection $Server -searchFilter “(&(objectClass=computer)(!(ms-MCS-AdmPwdExpirationTime=*)))” -searchBase $searchBase -PropertiesToLoad @(‘canonicalname’,‘lastlogontimestamp’))
$expiredNotRefreshed=@(Find-LdapObject -LdapConnection $Server -searchFilter “(&(objectClass=computer)(ms-MCS-AdmPwdExpirationTime<=$ts))” -searchBase $searchBase -PropertiesToLoad @(‘canonicalname’,‘lastlogontimestamp’))
#Write the LAPS information (summary and detail) to a temporary file in the previously specified share
$Content=@”
COUNTS
——
Enrolled: $($enrolledComputers.Count)
Not enrolled: $($nonEnrolledComputers.Count)
Expired: $($expiredNotRefreshed.Count)
DETAILS
——-
Enrolled
——–
$($enrolledComputers | Select-Object ‘canonicalname’,@{l=‘lastlogon’; e={[datetime]::FromFileTime($_.lastlogontimestamp).ToString(“MM-dd-yy”)}} | Out-String)
Not enrolled
————
$($nonEnrolledComputers | Select-Object ‘canonicalname’,@{l=‘lastlogon’; e={[datetime]::FromFileTime($_.lastlogontimestamp).ToString(“MM-dd-yy”)}} | Out-String)
Expired
——-
$($expiredNotRefreshed | Select-Object ‘canonicalname’,@{l=‘lastlogon’; e={[datetime]::FromFileTime($_.lastlogontimestamp).ToString(“MM-dd-yy”)}} | Out-String)
“@
$FileDate = (Get-Date).tostring(“MM-dd-yyyy-hh-mm-ss”)
$Filename=$Fileshare+‘’+$Filedate+‘LAPSReport.txt’
Add-Content -Value $Content -Path $Filename
If ($SendMessage)
{
#Edit the variables below to specify the email addresses and SMTP server to use
$EmailFrom = ‘lapshealth@tailspintoys.com’
$EmailTo=’emailaddress@tailspintoys.com’
$today = Get-Date
$EmailSubject = ‘LAPS Health Report for ‘ + $today.ToShortDateString()
$EmailBody=$Content
$smtpserver = “smtp.tailspintoys.com”
Send-MailMessage -Body $EmailBody -From $EmailFrom -To $EmailTo -Subject $EmailSubject -SmtpServer $smtpserver
}