Functions/Get-ADUsnNumberUpdate.ps1
<# .SYNOPSIS Uses the USN propertiy of Active Directory to monitor the updates. .DESCRIPTION Uses the update sequence number (USN) propertiy of Active Directory to monitor the updates. Whenever an object is changed, its USN is incremented. Each domain controller maintains its own USN, they cannot be conpared between each other. .PARAMETER Partition Define the target partition to monitor the changes. .PARAMETER ComputerName Define the computer names for the Domain Controllers where the changes were monitored. .PARAMETER FilterRegex Filter the objects inside the partition based on the distinguished name with a regex filter. With the default filter value, all objects will be displayed. .PARAMETER FilterWildcard Filter the objects inside the partition based on the distinguished name with a wildcard filter. With the default filter value, all objects will be displayed. .PARAMETER CookieFile The path to the cookie file. If the cookie exist, the current USN number per domain controller will be loaded and the update scripts starts at this USN number. If no file exists, a new cookie file will be created. If no path will be specified, no cookie file will be used. .PARAMETER CookieReadOnly If this switch is specified, the cookie file will only be readed and not updated with the new USN numbers. .PARAMETER Once The script executs only one search loop. .EXAMPLE C:\> Get-ADUsnNumberUpdate Start change monitoring with default values. .EXAMPLE C:\> Get-ADUsnNumberUpdate -Partition 'DC=adds,DC=contoso,DC=com' -ComputerName 'DC21.adds.contoso.com' Endless change monitoring every second inside the given partition targeting the DC21 domain controller. .EXAMPLE C:\> Get-ADUsnNumberUpdate -Partition 'DC=adds,DC=contoso,DC=com' -ComputerName 'DC21.adds.contoso.com' -CookieFile 'cookie.xml' -Once Create a cookie file for the current state of the partition. .EXAMPLE C:\> Get-ADUsnNumberUpdate -Partition 'DC=adds,DC=contoso,DC=com' -ComputerName 'DC21.adds.contoso.com' -CookieFile 'cookie.xml' -CookieReadOnly -Once Get all changes inside the partition which has been performed since the cookie creation. Preserve the cookie value. .EXAMPLE C:\> Get-ADUsnNumberUpdate -Partition 'DC=adds,DC=contoso,DC=com' -FilterWildcard '*OU=Test,DC=adds,DC=contoso,DC=com' Endless change monitoring but only for the test OU. With this filter, deleting objects is not reported. .NOTES Author : Claudio Spizzi License : MIT License Source : Microsoft Exchange FAQ, Author: Frank Carius, Website: http://www.msxfaq.de This function is a complete rewrite of the original script 'Get-USNChanges' provided by Frank Carius. Credits for initial concept and idea goes to him. .LINK https://github.com/claudiospizzi/ActiveDirectoryFever .LINK http://www.msxfaq.de/tools/get-usnchanges.htm #> function Get-ADUsnNumberUpdate { [CmdletBinding()] param ( [Parameter(Mandatory=$false)] [String] $Partition = ([ADSI] "LDAP://$env:USERDOMAIN/RootDSE").DefaultNamingContext, [Parameter(Mandatory=$false)] [String[]] $ComputerName = ([ADSI] "LDAP://$env:USERDOMAIN/RootDSE").DnsHostName, [Parameter(Mandatory=$false)] [String] $FilterRegex = ".*", [Parameter(Mandatory=$false)] [String] $FilterWildcard = "*", [Parameter(Mandatory=$false)] [AllowEmptyString()] [String] $CookieFile = "", [Parameter(Mandatory=$false)] [Switch] $CookieReadOnly, [Parameter(Mandatory=$false)] [Switch] $Once ) begin { # Counter variable to display the progress bar $Count = 0 # Global hashtables to save the cookies and the serarcher objects per domain controller $Cookie = @{} $Searcher = @{} # The Active Directory properties to load $Properties = "DistinguishedName", "USNChanged", "USNCreated", "WhenChanged", "IsDeleted", "ObjectClass", "ObjectCategory", "ObjectGuid", "ObjectSID", "SamAccountName", "LastKnownParent", "msDS-LastKnownRDN" # Check the cookie file variable # - No path specified: Do not use a cookie file and do not store the USN numbers # - Path specified, file existing: Load the latest USN numbers from the cookie file # - Path specified, file not found: Create a new cookie file but start at the lastest USN number on the Domain Controller if ($CookieFile -ne "" -and (Test-Path -Path $CookieFile)) { try { $Cookie = [Hashtable] (Import-Clixml -Path $CookieFile) } catch { Write-Error "Error while loading the cookie file: $_" return } } # Initialize the directory searcher object for each Domain Controller. Define the searcher to point # to the correct partition on the specified Domain Controllers and set the necessary properties. foreach ($Computer in $ComputerName) { # Load the USN number from the Domain controller, if the cookie file does not contain this USN number if (-not $Cookie.ContainsKey($Computer)) { $RootDSE = [ADSI] "LDAP://${Computer}/RootDSE" $Cookie[$Computer] = [Int64] $RootDSE.HighestCommittedUSN[0] } # Initialize directory searcher $Searcher[$Computer] = New-Object -TypeName System.DirectoryServices.DirectorySearcher -ArgumentList ([ADSI] "LDAP://${Computer}/${Partition}") $Searcher[$Computer].PropertiesToLoad.AddRange($Properties) $Searcher[$Computer].Sort.PropertyName = "USNChanged" $Searcher[$Computer].PageSize = 1000 $Searcher[$Computer].Tombstone = $true } } process { do { foreach ($Computer in $ComputerName) { Write-Progress -Activity "Active Directory Update Searcher..." -Status "Search for USN updates in $Partition on $Computer" -PercentComplete (($Count++) % 100) # Define the directory searcher filter with a new highest usn $Searcher[$Computer].Filter = "(&(|(IsDeleted=*)(!IsDeleted=*))((!USNChanged<=$($Cookie[$Computer]))))" # Execute the search and filter all objects with no USN number $SearchResult = $Searcher[$Computer].FindAll() | Where-Object { $_.Properties["USNChanged"] -ne "" } # Filter search result with input filter (regex & wildcard) $SearchFilter = $SearchResult | Where-Object { $_.Properties["DistinguishedName"] -like $FilterWildcard -and $_.Properties["DistinguishedName"] -match $FilterRegex } # Iterating all result objects, parse the properties and return a object to the pipeline if ($SearchFilter.Count -gt 0) { # Iterating all objects foreach ($SearchObject in $SearchFilter) { # Create an output object $Object = New-Object -TypeName PSObject -Property @{ Timestamp = $SearchObject.Properties["WhenChanged"][0] ObjectClass = $(try { $SearchObject.Properties["ObjectCategory"][0].Substring(3).Split(",")[0] } catch { "Unknown" }) ObjectGuid = [String] [Guid] $SearchObject.Properties["ObjectGUID"][0] ObjectSid = [String] (New-Object -TypeName "System.Security.Principal.SecurityIdentifier" -ArgumentList ($SearchObject.Properties["ObjectSID"][0]) , 0) Identity = $SearchObject.Properties["DistinguishedName"][0] Account = $SearchObject.Properties["SamAccountName"][0] Action = "" Field = "USNChanged" Value = $SearchObject.Properties["USNChanged"][0] } # Update the action and identity (if necessary) if ($SearchObject.Properties["USNChanged"][0] -eq $SearchObject.Properties["USNCreated"][0]) { $Object.Action = "CREATE" } elseif ($SearchObject.Properties["IsDeleted"][0] -eq $null) { $Object.Action = "MODIFY" } else { $Object.Action = "DELETE" $Object.Identity = "CN=" + $SearchObject.Properties["msDS-LastKnownRDN"] + "," + $SearchObject.Properties["LastKnownParent"] } $Object.PSTypeNames.Insert(0, "ActiveDirectoryFever.GetADUpdate.Result") Write-Output $Object } # Update the cookie variable $Cookie[$Computer] = $SearchResult[-1].Properties["USNChanged"] } # Update the cookie file if necessary if (($CookieFile -ne "") -and (-not $CookieReadOnly)) { $Cookie | Export-Clixml -Path $CookieFile -Encoding Unicode } } Start-Sleep -Seconds 1 } until ($Once) } } |