The Problem
Have you ever had to repopulate a batch of corrupted attributes for a large set of Active Directory objects? (Think Exchange or Lync, for example.) The Active Directory Recycle Bin is great for recovering deleted objects, but it will not help with corrupted objects. Authoritative restore is the textbook option, but there is a better way. Yes, you can buy expensive third-party products to do this, or you can use the free features in the box for your own attribute-level recovery solution for Active Directory. This blog post will explain how.
The Scenario
How do objects in Active Directory get corrupted? Let me count the ways:
- Change controls at 3PM during universal nap time… half-awake-barely-focused-click-OOPS!
- Joe Jr. Admin thought the change in the console would only affect his local view of the data.
- “What does this button do?”
- Bad feed from HR provisioning system
- One word: proxyAddresses
- Freak scripting accident
- Use the comments below to fill in the blank with your attribute corruption story.
That’s right. And there’s a ton more. It happens to the best of us at one point or another in our career. Whether we are the one that pulled the trigger or not, we will be the one to clean up the mess.
Oh Snap!
Microsoft has a long and winding road of Active Directory innovation designed to help those of us fighting in the daily trenches of IT. We have released a number of features to help with Active Directory recovery scenarios:
- “Protect from accidental deletion”
- Active Directory Recycle Bin
- Active Directory Snapshots
Wait! Snapshots! I thought we were never ever ever supposed to take snapshots of AD?! Regarding virtualization that is correct, or was correct, until Windows Server 2012 when we introduced VMGenID. But I’m talking about an entirely different type of snapshot.
Way back in Windows Server 2008 we introduced the ability to freeze frame (80’s reference for this post) the Active Directory database. Using the Volume Shadow Copy service we snapshot the AD database for future reference. Like every other feature on TechNet there is an involved manual process to create these snapshots and then use them later.That’s where PowerShell comes to the rescue.
Note: This feature has been around for years. I am not the first to blog about it, nor am I the first to use PowerShell with NTDSUTIL SNAPSHOT. However, I wanted to offer a complete, original attribute-level recovery solution using PowerShell for the benefit of the community.
I would like to give a quick shout-out to a PFE peer who gave me a couple tips while developing this code. Thanks, Eric Jansen!
The Solution
Using PowerShell we can automate the entire AD snapshot and attribute recovery process. Here is a simplified outline of the commands involved:
- NTDSUTIL SNAPSHOT CREATE
- NTDSUTIL SNAPSHOT MOUNT
- DSAMAIN.EXE -LDAPPort Port#
- Get-ADObject -Server DC1:Port#
- Set-ADObject -Server DC1
- Get-Process DSAMAIN | Kill
- NTDSUTIL SNAPSHOT UNMOUNT
Those steps are carried out by the following PowerShell functions included in this release:
- New-ADSnapshot – Creates a new snapshot using NTDSUTIL.
- Mount-ADDatabase – Mounts the snapshot using NTDSUTIL and advertises it using DSAMAIN. Note that DSAMAIN will launch in its own window. DO NOT CLOSE IT. It will remain open until closed by Dismount-ADDatabase. Specify a port for the snapshot to use for LDAP. In the examples below I use 33389.
- Show-ADSnapshot – Lists all snapshots on a domain controller, noting any currently mounted.
- Dismount-ADDatabase – Kills the DSAMAIN process and dismounts the database using NTDSUTIL.
- Remove-ADSnapshot – Removes old snapshots using NTDSUTIL. Parameters specify how many of the first/last snapshots to keep.
- Repair-ADAttribute – The main function for recovering AD object attribute values from snapshots. Supports single and multi-value attributes. Can restore multiple attribute values for multiple objects simultaneously.
- Repair-ADUserGroup – A specialized function addressing recovery of group memberships for users. This can also be accomplished using the Repair-ADAttribute function.
All functions are fully documented in help. You can use Get-Help -Full to view the details, syntax and examples of each function.
This gets even better when you add PowerShell remoting to the mix. From a Windows 7 or Windows 8 workstation you can run these commands locally on a domain controller using PowerShell remoting. All you need is a Windows Server 2008 R2 (or newer) domain controller with remoting enabled. These functions were designed to be backwards-compatible with PowerShell v2 (Windows 7 / Windows Server 2008 R2). The only exception is the Mount-ADDatabase -Filter parameter which requires PowerShell v3. I specifically chose to release this as a function library script rather than a module. This makes the code more portable when targeting DCs over a remoting session as demonstrated below.
Example Code
The following code illustrates the basic functions:
# Running locally from a domain controller... # Dot source a reference to the function library . .\AD_Snapshot_Functions.ps1 # Create a new snapshot and view it in the list New-ADSnapshot Show-ADSnapshot | Out-GridView Show-ADSnapshot -WMI | Out-GridView # Mount the database Get-Help Mount-ADDatabase -Full Mount-ADDatabase -Last -LDAPPort 33389 # Notice the snapshot list now shows which one is mounted Show-ADSnapshot | Out-GridView Show-ADSnapshot -WMI | Out-GridView # View a user in both copies of the database Get-ADUser Guest -Properties Description -Server localhost:33389 Get-ADUser Guest -Properties Description -Server localhost # Repair a single attribute for a single account Get-ADUser Guest -Server localhost | Repair-ADAttribute -Property Description -LDAPPort 33389 # Repair multiple attributes for multiple users Get-ADUser -Filter {name -like "G*"} | Repair-ADAttribute -Property Department,Description -LDAPPort 33389 # Finish cleanly Dismount-ADDatabase
When you recover attributes I recommend that you use the -WhatIf switch as a test run before actually making any changes.
The output from Repair-ADAttribute contains the following properties for each attribute per object:
- Action – Replaced, No Change, Not Found in Current (deleted since snapshot), Not Found in Snapshot (new since snapshot)
- Property – Attribute name
- ObjGUID – Active Directory object GUID
- NewObject – Distinguished name path to the current object
- OldObject – Distinguished name path to the snapshot object
- NewValue – Attribute value of the current object prior to any changes
- OldValue – Attribute value of the snapshot object
- Moved – Is the distinguished name path different between both databases?
You can send this rich output to the console, Out-Gridview, CSV, etc. Here is an example of the console output:
Action : Replaced Property : Department ObjGUID : 21e759e9-427e-4f07-aee3-cd95132d2f50 NewObject : CN=gusingh,OU=Sales,OU=Office,DC=wingtiptoys,DC=local OldObject : CN=gusingh,OU=Sales,OU=Office,DC=wingtiptoys,DC=local NewValue : Engineering OldValue : Sales Moved : False
Here is an example using PowerShell remoting:
$cred = Get-Credential wingtiptoys\administrator $s = New-PSSession -ComputerName dca.wingtiptoys.local -Credential $cred Invoke-Command -Session $s -FilePath '.\AD_Snapshot_Functions.ps1' Invoke-Command -Session $s -ScriptBlock {Show-ADSnapshot} Invoke-Command -Session $s -ScriptBlock {Mount-ADDatabase -Last -LDAPPort 33389} Invoke-Command -Session $s -ScriptBlock {Get-ADUser Guest | Repair-ADAttribute -Property Department,Description -LDAPPort 33389} Invoke-Command -Session $s -ScriptBlock {Dismount-ADDatabase} Remove-PSSession $s
There are many ways to conduct your remoting session. You can script out the entire activity to simplify this further.
You can even use a remoting session against multiple DCs simultaneously to perform your scheduled snapshots from a central location. This is the easiest way to schedule your snapshots.
# Blast a new snapshot and purge to all DCs # Scheduled task will need appropriate credentials $DCs = Get-ADDomainController -Filter * | Select-Object -ExpandProperty Hostname $s = New-PSSession -ComputerName $DCs Get-PSSession Invoke-Command -Session $s -FilePath '.\AD_Snapshot_Functions.ps1' # Set ThrottleLimit to 1 to preserve the order of output returned Invoke-Command -Session $s -ScriptBlock ` {"`n`n*********";hostname;New-ADSnapshot} -ThrottleLimit 1 Invoke-Command -Session $s -ScriptBlock ` {"`n`n*********";hostname;Remove-ADSnapshot -Keep 3 -Last} -ThrottleLimit 1 Invoke-Command -Session $s -ScriptBlock ` {"`n`n*********";hostname;Show-ADSnapshot} -ThrottleLimit 1 Remove-PSSession $s
How cool is that! Now take this sample code, add some logging, and tailor it to your environment. A full demo script is included with the download at the TechNet Script Center.
Public Service Announcement
This is the most important part of the entire article. Go do these things immediately to prepare for your own Active Directory recovery scenarios:
- Enable the AD Recycle Bin.
- Schedule AD snapshots and snapshot cleanup.
- Practice with these PowerShell functions in your lab.
- Tell your peers, friends, and grandmother to do likewise.
You cannot recover AD attributes unless you first have a snapshot. Well, you can, but that is a much longer process of AD authoritative restore. Save yourself some recovery time. Schedule your AD snapshots today.
Conclusion
The benefits of this solution are many:
- Speedy recovery time.
- Less disk space than full backups.
- Can copy to another server to view.
- View snapshots in LDP, ADUC, ADSIEDIT, or PowerShell.
- No reboots required.
- It’s free!
Here are a couple tips to keep in mind:
- You must recover from a snapshot created PRIOR TO the corruption event.
- The recovered attribute value is a new write, not a true recovery.
- Snapshots are read-only.
- Monitor disk space.
- Use Enterprise Admin or Domain Admin credentials.
- Snapshots are not a full recovery solution.
- When mounting the database pick a port not in use.
- Enable PowerShell remoting on DCs for ease of recovery.
- You can only update "write-able" attributes.
You do not need to schedule snapshots on every domain controller in the company, but I would recommend at least one or two domain controllers per domain per physical site. This will give you more options in the event of a recovery scenario. I would also recommend a weekly interval at the longest. Daily might be a bit much depending on the size of your database. Obviously I would schedule this for after hours.
Be the hero. Be ready. Schedule AD snapshots today.
Download the code and demo script from the TechNet Script Center here.
PS - I am really excited about this code. Everyone with Active Directory needs to prepare for attribute recovery. Please help me get the word out using the social links below. Thanks!
Resources
Active Directory Domain Services Database Mounting Tool (Snapshot Viewer or Snapshot Browser) Step-by-Step Guide - http://technet.microsoft.com/en-us/library/cc753609(WS.10).aspx
TechNet NTDSUTIL SNAPSHOT syntax - http://technet.microsoft.com/en-us/library/cc731620(WS.10).aspx
Update
Feb 3, 2015 - Updated code to support AD database stored on drive other than C:.
PowerShell script for getting Active Directory information
For a work project, I needed to compare Active Directory actual information to what was present in our ERP system, as well as match that with information about the user's Exchange 2003 mailbox.
I wrote a "down and dirty" PowerShell script to extract a number of fields from Active Directory and write the extracted information into a CSV file. My overall plan was to compare the three data sets — the Active Directory information, the Exchange mailbox information, and the ERP information — using Excel, while making sure there was information in all three data sets that would link the data sets to each other.
Here is more information about the project, followed by the PowerShell script I wrote.
Project details
Our reasons for this project:
- The organization has 16,000 Exchange mailboxes, and we wanted to ensure that only users who should have mailboxes do.
- We also wanted to ensure that Active Directory accounts for departed employees are inactive and are marked for removal.
These were the project challenges:
- In a separate report, I had to use WMI to gather Exchange mailbox information since Exchange 2003 doesn't include PowerShell.
- The organization has more than 600,000 user accounts in Active Directory, most of which are valid; only about 20,000 of these accounts are employees, while the rest are customers. However, in some cases, the customers were also temporary employees, so there was a need to search the entire Active Directory database for potential employee accounts.
A look at the PowerShell script
Notes: This PowerShell script was intended for one-time use and that creates a very different development environment, at least to me. I was going for immediate functionality rather than elegance (I am not a programmer), which is why I consider this a "down and dirty" PowerShell script.
I'll take a line-by-line (or, in some cases, a section-by-section) look at what this PowerShell script does and explain my thinking.
# Start of script
I needed to clear the screen before script execution to make sure there was no clutter that would confuse me when I looked at display results.
Cls
I added a processing loop to break down the Active Directory information into usable chunks. Prior to adding this loop, my script crashed because the machine on which I was running it ran out of memory trying to handle more than 600,000 records at once. Each item in the "targetou" section is an Active Directory organizational unit. Immediately below, you will see a line that outputs to the screen that OU is currently being processed. By displaying information at run-time, I know exactly where I am in a process.
foreach ($targetou in 'A','B','C','D','E','F','G','GUESTACCOUNTS','H','I','J','K','L','CONTRACTOR','M','N','O','P','Q','R','S','T',','U','V','W','X','Y','Z'){"Processing information for OU $targetou"
The $targetou variable above is the lowest point in the Active Directory hierarchy at which I worked. The $domainrootpath variable builds the full LDAP string to the OU against which the script was to run for each iteration.
$DomainRootPath='LDAP://OU='+$targetou+',OU=ORGUSER,DC=contoso,DC=com'
The next several lines create and populate an Active Directory searcher object in PowerShell.
$adsearch = New-Object DirectoryServices.DirectoryAdsearch([adsi]$DomainRootPath)
I limited the kinds of objects that would be returned. The line below limits results to user objects.
$adsearch.filter = "(objectclass=user)"
The PropertiesToLoad items below were necessary for the reporting task I had ahead of me. These lines modify the behavior of the Active Directory search by forcing it to return only what is specified rather than returning everything. Because of the size of the data set, I needed to limit the returned data to only what was essential.
$adsearch.PropertiesToLoad.AddRange(@("name"))$adsearch.PropertiesToLoad.AddRange(@("lastLogon"))$adsearch.PropertiesToLoad.AddRange(@("givenName"))$adsearch.PropertiesToLoad.AddRange(@("SN"))$adsearch.PropertiesToLoad.AddRange(@("DisplayName"))$adsearch.PropertiesToLoad.AddRange(@("extensionAttribute1"))$adsearch.PropertiesToLoad.AddRange(@("extensionAttribute2"))$adsearch.PropertiesToLoad.AddRange(@("comment"))$adsearch.PropertiesToLoad.AddRange(@("title"))$adsearch.PropertiesToLoad.AddRange(@("mail"))$adsearch.PropertiesToLoad.AddRange(@("userAccountControl"))$adsearch.Container
This line executes the search based on the parameters specified above. For each iteration of the foreach loop, Active Directory will search the organizational unit for that loop and return all of the attributes specified above for each user account. The results of the execution will be stored in the variable named users. Unfortunately, as it exists, the information from this array can't be simply written to a CSV file since that CSV file would contain only the Active Directory object name and an entry called "System.DirectoryServices.ResultPropertyCollection." I needed to expand out and capture the individual Active Directory elements, which I do later in the script.
$users = $adsearch.findall()
As the script was running, I wanted to know how many objects were returned from each loop iteration, so I added the line below to show how many user accounts were being handled.
$users.Count
I initialized an array variable into which I'd write the individual Active Directory elements we wanted to capture.
$report = @()
I started another loop that executes for each Active Directory account for which we wanted to capture information.
foreach ($objResult in $users) {
I needed to create a variable that houses the properties for an individual record. (There are other ways to do this, but I like to break things down to make them more readable.)
$objItem = $objResult.Properties
I created a new temporary object into which to write the various Active Directory attributes for this single record being processed in this processing iteration (remember, this is repeated for each record returned from Active Directory).
$temp = New-Object PSObject
For each individual Active Directory property that was returned from the Active Directory searcher, I added a named property to the temp variable for this loop iteration. Basically, this breaks out the single Active Directory record for a user into its individual components, such as name, title, email address, and so forth. (Case-sensitivity matters in this section.)
$temp | Add-Member NoteProperty name $($objitem.name)$temp | Add-Member NoteProperty title $($objitem.title)$temp | Add-Member NoteProperty mail $($objitem.mail)$temp | Add-Member NoteProperty displayname $($objitem.displayname)$temp | Add-Member NoteProperty extensionAttribute1 $($objitem.extensionattribute1)$temp | Add-Member NoteProperty extensionAttribute2 $($objitem.extensionattribute2)$temp | Add-Member NoteProperty givenname $($objitem.givenname)$temp | Add-Member NoteProperty sn $($objitem.sn)$temp | Add-Member NoteProperty useraccountcontrol $($objitem.useraccountcontrol)
I added the results of this individual record to the primary array into which we're capturing the full results from the search for later export to CSV.
$report += $temp }
This line creates the name of the file that will be written. I created a new file for each organizational unit processed.
$csvfile="AD-"+$targetou+".csv"
The line writes the entire file to disk and then notifies the user that processing for this OU has completed.
$report | export-csv -notypeinformation $csvfile"Wrote file for $targetou"}
Summary
For my purposes, this PowerShell script captured exactly the information that I needed, and I was able to complete my comparison task.
No comments:
Post a Comment