├── .gitattributes
├── .gitignore
├── Create-TestLaps.ps1
├── LAPS Audit Dashboard.pbix
├── LAPSParsingTask.xml
├── LAPSQuery.ps1
├── LAPSQueryTask.xml
├── LAPSReporting.ps1
├── LAPSServerSetup.ps1
├── LAPSSubscription.xml
└── README.md
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 |
7 | # Standard to msysgit
8 | *.doc diff=astextplain
9 | *.DOC diff=astextplain
10 | *.docx diff=astextplain
11 | *.DOCX diff=astextplain
12 | *.dot diff=astextplain
13 | *.DOT diff=astextplain
14 | *.pdf diff=astextplain
15 | *.PDF diff=astextplain
16 | *.rtf diff=astextplain
17 | *.RTF diff=astextplain
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Windows image file caches
2 | Thumbs.db
3 | ehthumbs.db
4 |
5 | # Folder config file
6 | Desktop.ini
7 |
8 | # Recycle Bin used on file shares
9 | $RECYCLE.BIN/
10 |
11 | # Windows Installer files
12 | *.cab
13 | *.msi
14 | *.msm
15 | *.msp
16 |
17 | # Windows shortcuts
18 | *.lnk
19 |
20 | # =========================
21 | # Operating System Files
22 | # =========================
23 |
24 | # OSX
25 | # =========================
26 |
27 | .DS_Store
28 | .AppleDouble
29 | .LSOverride
30 |
31 | # Thumbnails
32 | ._*
33 |
34 | # Files that might appear on external disk
35 | .Spotlight-V100
36 | .Trashes
37 |
38 | # Directories potentially created on remote AFP share
39 | .AppleDB
40 | .AppleDesktop
41 | Network Trash Folder
42 | Temporary Items
43 | .apdisk
44 |
--------------------------------------------------------------------------------
/Create-TestLaps.ps1:
--------------------------------------------------------------------------------
1 | # Use to create a series of test OU's with computer objects, set the password attribute to something on a number of them and then perform a read on the password attributes
2 | # Useful for testing auditing as well as a querying of set passwords in an environment
3 |
4 | for($i=100; $i -le 120; $i++){
5 |
6 | New-ADOrganizationalUnit -Path "dc=contoso,dc=com" -Name LAPS$i
7 | for($c=1; $c -le 100; $c++){
8 | new-ADComputer -path "ou=laps$i,dc=contoso,dc=com" -Name LAP$i$c
9 | }
10 | for($b=1; $b -le 50; $b++){
11 | {SET-ADcomputer LAPS$i$b –replace @{'ms-Mcs-AdmPwd'=”STUFF”}
12 | Get-ADComputer LAPS$i$b -Properties ms-Mcs-AdmPwd}
13 | }
14 | }
15 |
16 | <#
17 | Use the following to delete this test environment if needed
18 |
19 | for($i=100; $i -le 120; $i++){
20 |
21 | Set-ADObject -ProtectedFromAccidentalDeletion $false -Identity "OU=LAPS$i,dc=contoso,dc=com"
22 |
23 | remove-ADOrganizationalUnit -Identity "OU=LAPS$i,dc=contoso,dc=com" -Recursive -Confirm:$false
24 |
25 | }
26 | #>
--------------------------------------------------------------------------------
/LAPS Audit Dashboard.pbix:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kurtfalde/LAPS-Reporting/297ee01e65b534059c0e312c8510de721d4bf5d7/LAPS Audit Dashboard.pbix
--------------------------------------------------------------------------------
/LAPSParsingTask.xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kurtfalde/LAPS-Reporting/297ee01e65b534059c0e312c8510de721d4bf5d7/LAPSParsingTask.xml
--------------------------------------------------------------------------------
/LAPSQuery.ps1:
--------------------------------------------------------------------------------
1 |
2 | <#
3 |
4 | Script to query various data points for the LAPS password attributes in Active Directory.
5 | By default this queries for all computers in the domain the script is ran from
6 |
7 |
8 |
9 | #>
10 |
11 | cd C:\laps
12 | del .\lapsquery.csv
13 |
14 | $computers = Get-ADComputer -Filter * -Properties Name,ms-Mcs-AdmPwdExpirationTime,msDS-ReplAttributeMetaData
15 |
16 | Foreach ($computer in $computers){
17 |
18 |
19 | $CompObj = New-Object psobject
20 |
21 | $CompObj | Add-Member noteproperty Computername $computer.Name
22 |
23 | If($computer.'ms-Mcs-AdmPwdExpirationTime' -eq $null){$ExpirationDate = $computer.'ms-Mcs-AdmPwdExpirationTime'}
24 | Else {$ExpirationDate = [DateTime]::FromFileTime($computer.'ms-Mcs-AdmPwdExpirationTime')}
25 | $CompObj | Add-Member noteproperty NextPasswordExpiration $ExpirationDate
26 |
27 |
28 | $xmlAttribute = $computer."msDS-ReplAttributeMetaData"
29 | $xmlAttribute = “” + $xmlAttribute + “”
30 | $xmlAttribute = $xmlAttribute.Replace([char]0,” ”)
31 | $xmlattribute =[xml]$xmlattribute
32 |
33 | $OriginatingDCPwdLastSet = $null
34 | $OriginatingDCSiteLastSet = $null
35 | $LastTimePwdModified = $null
36 |
37 | foreach ($attribute in $xmlAttribute.root.DS_REPL_ATTR_META_DATA | Where-Object {$_.pszAttributeName -eq "ms-Mcs-AdmPwd"}){
38 |
39 | $OriginatingDCPwdLastSet = ($attribute.pszLastOriginatingDsaDN.Split(','))[1] -replace '^cn='
40 | $OriginatingDCSiteLastSet = ($attribute.pszLastOriginatingDsaDN.Split(','))[3] -replace '^cn='
41 | $LastTimePwdModified = $attribute.ftimeLastOriginatingChange
42 |
43 |
44 | }
45 |
46 | $CompObj | Add-Member noteproperty OriginatingDCLastPwdSet $OriginatingDCPwdLastSet
47 | $CompObj | Add-Member noteproperty OriginatingDCSiteLastSet $OriginatingDCSiteLastSet
48 | $CompObj | Add-Member noteproperty LastTimePwdModified $LastTimePwdModified
49 |
50 | $ComputerOU = $computer.DistinguishedName -creplace "^[^,]*,",""
51 | $CompObj | Add-Member NoteProperty ComputerOU $ComputerOU
52 |
53 | $CompObj | Write-Host
54 | $compobj | Export-Csv .\lapsquery.csv -Append -NoTypeInformation
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/LAPSQueryTask.xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kurtfalde/LAPS-Reporting/297ee01e65b534059c0e312c8510de721d4bf5d7/LAPSQueryTask.xml
--------------------------------------------------------------------------------
/LAPSReporting.ps1:
--------------------------------------------------------------------------------
1 | <#
2 | This Sample Code is provided for the purpose of illustration only and is not
3 | intended to be used in a production environment. THIS SAMPLE CODE AND ANY
4 | RELATED INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
5 | EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF
6 | MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. We grant You a
7 | nonexclusive, royalty-free right to use and modify the Sample Code and to
8 | reproduce and distribute the object code form of the Sample Code, provided
9 | that You agree:
10 | (i) to not use Our name, logo, or trademarks to market Your
11 | software product in which the Sample Code is embedded;
12 | (ii) to include a valid copyright notice on Your software product
13 | in which the Sample Code is embedded; and
14 | (iii) to indemnify, hold harmless, and defend Us and Our suppliers
15 | from and against any claims or lawsuits, including attorneys’
16 | fees, that arise or result from the use or distribution of
17 | the Sample Code.
18 |
19 |
20 | Originally written by Kurt Falde Using PSEventLogWatcher from http://pseventlogwatcher.codeplex.com/
21 | New version for parsing LAPS Audit Log entries 9/20/2015 Kurt Falde MSFT Services
22 |
23 | #>
24 |
25 |
26 |
27 | $workingdir = "c:\laps"
28 |
29 | cd $workingdir
30 |
31 | #The following line was needed on a 2008 R2 setup at one customer It shouldn't hurt having it here however this can be removed in some cases
32 | Add-Type -AssemblyName System.Core
33 |
34 | #Following added to unregister any existing SLAMWatcher Event in case script has already been ran
35 | Unregister-Event LAPSWatcher -ErrorAction SilentlyContinue
36 | Remove-Job LAPSWatcher -ErrorAction SilentlyContinue
37 |
38 |
39 | Import-Module .\EventLogWatcher.psm1
40 |
41 | #Verify Bookmark currently exists prior to setting as start point
42 | $TestStream = $null
43 | $ECName = (((Get-Content .\bookmark.stream)[1]) -split "'")[1]
44 | $ERId = (((Get-Content .\bookmark.stream)[1]) -split "'")[3]
45 | $TestStream = Get-WinEvent -LogName $ECName -FilterXPath "*[System[(EventRecordID=$ERID)]]"
46 | If ($TestStream -eq $null) {Remove-Item .\bookmark.stream}
47 |
48 | $BookmarkToStartFrom = Get-BookmarkToStartFrom
49 |
50 | # The type of events that you want to parse.. in most cases we are parsing the Forwarded Events log with a specific xpath query for the events we want.
51 |
52 | $XpathQuery = "*[System[EventID=4662]] and *[EventData[Data[@Name='ObjectType'] and (Data='%{bf967a86-0de6-11d0-a285-00aa003049e2}')]]"
53 | $EventLogQuery = New-EventLogQuery "ForwardedEvents" -Query $XpathQuery
54 |
55 |
56 | $EventLogWatcher = New-EventLogWatcher $EventLogQuery $BookmarkToStartFrom
57 |
58 | $action = {
59 |
60 | #Following is debug line while developing as it will let you enter the nested prompt/session where you
61 | #can actually query the $EventRecord / $EventRecordXML etc for troubleshooting
62 | #$host.EnterNestedPrompt()
63 | $outfile = "c:\laps\laps.csv"
64 |
65 |
66 | $EventObj = New-Object psobject
67 | $EventObj | Add-Member noteproperty DomainController $EventRecord.MachineName
68 | $EventObj | Add-Member noteproperty User $EventRecordXml.SelectSingleNode("//*[@Name='SubjectUserName']")."#text"
69 |
70 | #Adding Date in Short Format to .csv object (Time is nice but it gets very messy when you start putting in Excel/PowerBI to aggregate down to hours/days etc)
71 | $EventObj | Add-Member noteproperty EventDate $EventRecord.TimeCreated.ToShortDateString()
72 |
73 | #Adding EventRecordID field to .csv object for a unique identifier
74 | $EventObj | Add-Member noteproperty EventRecordID $EventRecordXML.Event.System.EventRecordID
75 |
76 | #Adding Target Computer Object after resolving GUID to name
77 | $ComputerGUID = ($EventRecordXML.SelectSingleNode("//*[@Name='ObjectName']")."#text") -replace "%", "" -replace "{", "" -replace "}", ""
78 | $ComputerName = $null
79 | Try
80 | {
81 | $ComputerName = (get-adobject -id $ComputerGUID).Name
82 | }
83 | Catch
84 | {
85 | $ComputerName = $ComputerGUID
86 | }
87 | $EventObj | Add-Member noteproperty TargetComputer $ComputerName
88 |
89 |
90 | If ($Outfile -ne $Null)
91 | {
92 | write-host $EventObj
93 | $EventObj | Convertto-CSV -Outvariable OutData -NoTypeInformation
94 |
95 | $OutPath = $Outfile
96 | If (Test-Path $OutPath)
97 | {
98 | $Outdata[1..($Outdata.count - 1)] | ForEach-Object {Out-File -InputObject $_ $OutPath -append default}
99 | } else {
100 | Out-File -InputObject $Outdata $OutPath -Encoding default
101 | }
102 | }
103 |
104 |
105 | }
106 |
107 | Register-EventRecordWrittenEvent $EventLogWatcher -action $action -SourceIdentifier LAPSWatcher
108 |
109 | $EventLogWatcher.Enabled = $True
--------------------------------------------------------------------------------
/LAPSServerSetup.ps1:
--------------------------------------------------------------------------------
1 | <# Setup script for Subscription Server for LAPS Audit Events and reporting
2 |
3 | Microsoft Services - Kurt Falde
4 |
5 | #>
6 |
7 | #Please change the $DCTOQUERY value to the DC that has been used for Audit Testing
8 | $DCTOQUERY = "ADDC"
9 | cd C:\LAPS
10 |
11 | Import-Module ServerManager
12 | If ((Get-WindowsFeature -Name RSAT-AD-PowerShell).Installed -eq $false){
13 | write-host "This needs the AD Powershell commandlets to proceed please run Install-WindowsFeature -Name RSAT-AD_PowerShell"
14 | Exit
15 | }
16 |
17 | $rootdse = Get-ADRootDSE
18 | $Guids = Get-ADObject -SearchBase ($rootdse.SchemaNamingContext) -LDAPFilter "(schemaidguid=*)" -Properties lDAPDisplayName,schemaIDGUID
19 | ForEach ($Guid in $Guids){
20 | If ($guid.lDAPDisplayName -Like "*ms-mcs-admpwd"){
21 | $SGuid = ([System.GUID]$guid.SchemaIDGuid).Guid
22 | Write-host -ForegroundColor Cyan "The SchemaIDGuide for ms-mcs-admpwd in this forest is"([System.GUID]$guid.SchemaIDGuid)
23 | }
24 | }
25 |
26 | $xpath = "*[EventData[Data[@Name='AccessMask'] and (Data='0x100')]] and *[System[EventID=4662]] and *[EventData[Data[@Name='ObjectType'] and (Data='%{bf967a86-0de6-11d0-a285-00aa003049e2}')]]"
27 | $LAPSAuditEvents = Get-WinEvent -ComputerName $DCTOQUERY -LogName Security -FilterXPath "$xpath" -MaxEvents 200
28 |
29 | $xpathdata = @()
30 | :Outer Foreach($LAPSAuditevent in $LAPSAuditEvents){
31 | [xml]$LAPSAuditEventXML = $LAPSAuditEvent.ToXml()
32 | $EventXMLNames = $LAPSAuditEventXML.Event.EventData.Data
33 |
34 | #loop through the "Properties" XML Node looking for Events that contain the LAPS ms-mscadmpwd SchemaIDGuid
35 | #Add all hits to a new Array $xpathdata
36 |
37 | Foreach ($EventXMLName in $EventXMLNames){
38 | If (($EventXMLName.name -eq "Properties") -and ($EventXMLName.'#text' -match "$SGuid")){
39 | $LAPSProp = $EventXMLName.'#text'
40 | If ($xpathdata.count -eq 0){$xpathdata += $LAPSProp}
41 | ElseIf (($LAPSProp | Select-String -AllMatches $xpathdata) -eq $null){$xpathdata += $LAPSProp }
42 | }
43 | }
44 | }
45 |
46 | #Parsing through the $xpathdata array to create the right text needed for the xpath filter
47 | $xpathguid = $null
48 | If ($xpathdata.Count -le 1) {
49 | Write-host -ForegroundColor Cyan "Either a Single or No Xpath Filters created are you sure you tested LAPS UI and using ADUC at a minium on the DC you are testing? That or change the get-winevent line to a larger number of events"
50 | $xpathguid = "(Data='$xpathdata')"
51 | }
52 |
53 | ElseIf ($xpathdata.Count -gt 1) {
54 | Write-host -ForegroundColor Green "More than a single xpath filter found creating full xpath query output"
55 | Foreach ($xpaththing in $xpathdata){
56 | If ($xpaththing -ne $xpathdata[-1]){
57 | $xpathguid += "(Data='$xpaththing') or "
58 | }
59 | Else{$xpathguid += "(Data='$xpaththing')"}
60 | }
61 | }
62 |
63 | #FYI by default we are filtering out local queries to this from the "SYSTEM" account on a DC as this appears to happen with a DC reading it's own account in AD and creates noise
64 | #$xpathlaps = ""
65 | #$xpathlaps = $xpathlaps.Replace("`n", "
").Replace("`t", " ")
66 |
67 | #(gc C:\laps\LAPSSubscription.xml).replace('CHANGETHIS', $xpathlaps) | sc C:\laps\LAPSSubscription.xml -Encoding UTF8
68 |
69 | #Setting WinRM Service to automatic start and running quickconfig
70 | Set-Service -Name winrm -StartupType Automatic
71 | winrm quickconfig -quiet
72 |
73 | #Set the size of the forwarded events log to 500MB
74 | wevtutil sl forwardedevents /ms:500000000
75 |
76 |
77 | #Running quickconfig for subscription service
78 | wecutil qc -quiet
79 |
80 | #Creating Applocker Subscription from XML files FYI we do delete any existing ones and recreate
81 | If ((wecutil gs "LAPS Audit Events") -ne $NULL) {
82 | wecutil ds "LAPS Audit Events"
83 | wecutil cs .\LAPSSubscription.xml
84 | }
85 | Else {wecutil cs .\LAPSSubscription.xml}
86 |
87 | #Fix up the query in the registry with the proper text with carriage returns replaced.. carriage returns in the xml file imported via wecutil do not make it to the registry key
88 | $xpathguid = $xpathguid.Replace("`n","`r`n")
89 | $xpathlaps = ""
90 | Set-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\EventCollector\Subscriptions\LAPS Audit Events" -Name "Query" -Value $xpathlaps
91 | wecutil rs "LAPS Audit Events"
92 |
93 |
94 | #FYI if you need to export Subscriptions to fix SIDS or anything in an environment
95 | #use wecutil gs "%subscriptionname%" /f:xml >>"C:\Temp\%subscriptionname%.xml"
96 |
97 | #Creating Task Scheduler Item to restart LAPS parsing script on reboot of system.
98 | schtasks.exe /delete /tn "LAPS Parsing Task" /F
99 | schtasks.exe /create /tn "LAPS Parsing Task" /xml LAPSParsingTask.xml
100 | schtasks.exe /run /tn "LAPS Parsing Task"
101 |
102 | #Creating Task Scheduler Item to run LAPS query script on a daily basis.
103 | schtasks.exe /delete /tn "LAPS Query Task" /F
104 | schtasks.exe /create /tn "LAPS Query Task" /xml LAPSQueryTask.xml
105 | schtasks.exe /run /tn "LAPS Query Task"
--------------------------------------------------------------------------------
/LAPSSubscription.xml:
--------------------------------------------------------------------------------
1 |
2 | LAPS Audit Events
3 | SourceInitiated
4 | LAPS Audit Events
5 | true
6 | http://schemas.microsoft.com/wbem/wsman/1/windows/EventLog
7 |
8 |
9 | Custom
10 |
11 |
12 |
13 | 1
14 | 1000
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | true
26 | http
27 | RenderedText
28 |
29 | ForwardedEvents
30 |
31 | O:NSG:BAD:P(A;;GA;;;DD)S:
32 |
33 |
34 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # LAPS-Reporting
2 | Solution for Auditing LAPS usage in an Active Directory environment.
3 |
4 | Please see http://blogs.technet.com/b/kfalde/archive/2015/11/18/laps-audit-reporting-via-wef-posh-and-powerbi.aspx for
5 | an overview of how to configure this project to gather LAPS Local Administrator Password Solution data from an Active Directory
6 | environment.
7 |
--------------------------------------------------------------------------------