# .SYNOPSIS # Serach für modified Objects based on the USN # # .DESCRIPTION # Connects to a given DC or the PDC and partition using LDAP and integrated credentials. # polls für modifications and pipes them to the following process. # # .PARAMETER lastusn # Submit the last highest USN were to start from. # set to -1 (default) to start with current USN # set to 0 to start from scratch # use USN from former call pipeline to follow earlier changes # Overwrites USN from lastusnfile # # .PARAMETER usnfilename # Filepath and name to save and load USN from # # .PARAMETER domaincontroller # Specify a DC. is mus be the same, to have valid USN sequences # Default using "localhost" # # .PARAMETER ldappath # Optional an LDAP Path to query. DC must be added too # "LDAP://dname/dc=domain,dc=tld" # "GC://dcname" # # .PARAMETER csvfilename # Specify a CSV-File to log moifications. Default none # # .PARAMETER sleeptime # seconds to stay idle between two searches. default 3 seconds # # .PARAMETER once # Set to true or add switch to end after on search # only useful, if you also specif a lastusn # # .PARAMETER noscreen # disable output of objects to screen # # .PARAMETER verbose # Enable Verbose Output # # .EXAMPLE # PS C:\> get-usnchanges.ps1 -lastusn 10221 -csvdile .\usn.log # # .INPUTS # only parameters, no pipeline # # .OUTPUTS # Send Object to pipeline with Timestamp of processing, usn, lastmodified, dn # Sorted by USN ascending # # .NOTES # if not signed you may require Set-ExecutionPolicy RemoteSigned # You must have "read-Permissions" to the object you want to find. # # .LINK # See http://www.msxfaq.net/tools/get-usnchanges.htm # # # Version 1.0 20110626 based on former tests and scripts param( [long]$lastusn = "", # starting USN. USN=-1 -> skip old entries [string]$usnfilename = "", # name of the file to store USN [string]$domaincontroller = "localhost", # DC, by default use the local DC. [string]$ldappath="LDAP://localhost", # optional LDAP-Path, User current domain as default [string]$csvfilename = "", # optional CSV-File to track changes [int]$sleeptime = 3, # seconds to wait while sleeping [switch]$once, # quit after one run, useful with USN [switch]$noscreen, # disable output of objects to screen [switch]$verbose # Enable Verbose Output ) Set-PSDebug -Strict Write-Host "Get-USNChanges ---------------- Starting -----------------------" $VerbosePreference = "silentlycontinue" if ($verbose){$VerbosePreference = "continue"} $ErrorActionPreference = "stop" function generate-output { param( [string]$f1usn, [string]$f2class, [string]$f3lastmod, [string]$f4dn ) [datetime]$timestamp = (Get-Date).touniversaltime() [string]$csvfilenamedt = $csvfilename+(get-date -Format yyyyMMdd)+".csv" $pso = New-Object PSObject Add-Member -InputObject $pso noteproperty timestamp (get-date -Format s) Add-Member -InputObject $pso noteproperty usn $f1usn Add-Member -InputObject $pso noteproperty class $f2class Add-Member -InputObject $pso noteproperty lastmodified $f3lastmod Add-Member -InputObject $pso noteproperty dn $f4dn if ($csvfilename -ne "") { Write-Verbose -Message "Writing CSV-File $csvfilenamedt" if ((Get-Item -Path $csvfilenamedt -ErrorAction SilentlyContinue)-eq $null) { Write-Verbose -Message "Writing CSV-Header" [string]"datetime,usn,class,lastmod,dn" | out-file $csvfilenamedt } [string]$datetime = $timestamp.tostring() [string]$datetime+","+$pso.timestamp+","+$pso.usn+","+$pso.class+","+$pso.lastmodified +","+$pso.dn | out-file $csvfilenamedt -append } write-verbose -message "Sending output to pipeline" if (!$noscreen) { $pso | Out-Host } $pso } Write-Verbose " Preparing AD-Search" $objSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]$ldappath) $objSearcher.PageSize = 1000 Write-verbose " Loading required Properties in Search request START" $objSearcher.PropertiesToLoad.Add("distinguishedname") | Out-Null $objSearcher.PropertiesToLoad.Add("usnchanged") | Out-Null $objSearcher.PropertiesToLoad.Add("whenchanged") | Out-Null $objSearcher.PropertiesToLoad.Add("objectclass") | Out-Null $objSearcher.PropertiesToLoad.Add("objectcategory") | Out-Null # Serversorting can generate a lot of load #Write-verbose " Setting Sortdirection to Ascending" #$objSearcher.sort = New-Object System.DirectoryServices.SortOption("USNChanged", [System.DirectoryServices.SortDirection]::Ascending) [string]$lastfileusn = "" if ($usnfilename -ne "") { Write-Verbose -Message "Reading LastFileUSN from $usnfilename" $lastfileUSN = Get-Content $usnfilename -ErrorAction SilentlyContinue -TotalCount 1 if ($lastfileUSN -ne "") { Write-Verbose -Message " LastUSN File loaded: lastFileUSN:$lastfileUSN" } else { Write-Verbose -Message " LastUSN File not found." } } if ($lastusn -eq "") { Write-Verbose " USN-Start: no commandline." if ($lastfileUSN -eq "") { Write-Verbose " USN-Start: no file and not commandline. Skip older" $lastusn = "-1" } else { Write-Verbose " USN-Start: from File: $lastfileUSN" $lastusn = $lastfileUSN } } if ($lastUSN -eq "-1") { write-verbose " USN-Start: Skipping old entries, loading highestCommittedUSN from RootDSE" $root = [ADSI]"LDAP://$domaincontroller/RootDSE" $lastUSN = $root.highestCommittedUSN[0] } else { Write-Verbose " USN-Start: from parameter: $lastusn" } do { Write-verbose " Search für modified objects in Active directory from USN $lastUSN" $objSearcher.Filter = "(!USNChanged<=" + $lastUSN + ")" # greater than does not work so not smaller or equal work. $colResults = $objSearcher.FindAll() [int64]$total = $colResults.count Write-verbose "Get-USNChanges :Searching DONE: Total Object found: $total" [int64]$highusn = $lastUSN foreach ($objResult in $colResults) { $error.clear() [string]$adsPath = $objResult.Path Write-verbose "----- Processing Object: $adspath" [int64]$currentusn = $objResult.Properties.usnchanged[0] [datetime]$lastmod = $objResult.Properties.whenchanged[0] [string]$objclass = $objResult.Properties.objectclass [string]$objclass2 = $objResult.Properties.objectclass[$objResult.Properties.objectclass.count-1] [string]$dn = $objResult.Properties.distinguishedname [string]$objcategory = ($objResult.Properties.objectcategory[0].Replace("CN=","").Split(","))[0] Write-verbose " objclass : $objclass2" Write-verbose " dn : $dn" Write-verbose " usn : $currentusn" Write-verbose " WhenChaged: $lastmod" generate-output $currentusn $objclass2 $lastmod $dn Write-verbose " Processing Highest USN currentusn=$currentusn HighestUSN=$highusn" if ($currentusn -ge $highusn) { $highusn = $currentusn Write-verbose " updating HighestUSN to $highusn" } Write-Verbose " Processing Highest USN DONE" } if ($lastUSN -ne $highusn.tostring()){ write-verbose "Updateing LastUSN" $lastUSN = $highusn.tostring() } if ($usnfilename -ne "" -and $lastUSN -ne $lastfileusn) { Write-Verbose -Message "Writing LastUSN to file $usnfilename" $lastUSN | Out-File -FilePath $usnfilename } else { write-verbose "No modifications found" } Write-verbose 'DONE Processing. Updating status' $lastrun = (Get-Date -Format hh:mm:ss) for ($waitseconds = $sleeptime; (($waitseconds -gt 0)-and (!$once)) ; $waitseconds--) { write-host "`rGET-USNChanges: LastCheck:$lastrun USN:$lastUSN Wait:$waitseconds (E)nd(P)ause(C)ls" -NoNewline; Start-Sleep 1; if ($Host.UI.RawUI.KeyAvailable) { Write-Host `a switch ($Host.UI.RawUI.ReadKey().character.tostring().tolower()) { "e" { $once = $true} "p" { Write-Host "Press Enter to continue"; Read-Host} "c" { cls} default { Write-Host `a`a`a} } $Host.UI.RawUI.FlushInputBuffer() } } } until ($once) Write-host "Get-USNChanges -------------- finished --------------------"