Summary: Guest blogger, Alan Kaplan, talks about using Windows PowerShell with the Active Directory Recycle Bin.
Microsoft Scripting Guy, Ed Wilson, is here. Today we have a guest blog post written by Alan Kaplan. I met Alan several years ago when the Scripting Wife and Jim Christopher started the Windows PowerShell User Group in Charlotte, North Carolina. Since then, our path continue to cross when I see him at various regional IT group meetings (and at PowerShell Saturday). He is a real Windows PowerShell enthusiast, and he has grown his skills significantly since I first met him. Now he is a guest blogger. Take it away Alan…
In March of 2013, during a presentation to the Carolina IT Professionals User Group in Charlotte, NC, Ed Wilson convinced me that it was time to switch from VBScript to Windows PowerShell. I answered a few questions correctly, and Ed gave me an autographed copy of Windows PowerShell 3.0 Step by Step. He suggested that I join the Charlotte PowerShell User Group, which he and Teresa (The Scripting Wife) attend as time permits. One and a half years later, I was at the Windows PowerShell meeting where I discussed an earlier version of this script. Ed asked if I would be willing to write a post for the Hey, Scripting Guy! Blog, and after clearing it with my employer’s lawyers, I said, “Yes.”
The Active Directory Recycle Bin was introduced in Windows Server 2008 R2. The Recycle Bin must be first be enabled, and the only way to restore a deleted a user account is to use the Restore-ADObject cmdlet, with pretty arcane parameters. My goal was to write a script which would:
- Provide a GUI front end for Restore-ADObject
- Allow the user to choose one or more user accounts to restore
- Permit restoring from all domains within the forest for which an administrator has permissions
In the first version of this script, I used well-known GUIDs to refer to the location of the deleted object’s container and for the location of the user's container, which I chose to be the default location for the restored user object. (For more information about well-known GUIDs, see 6.1.1.4 Well-Known Objects.) This turned out to be unnecessary because Windows PowerShell makes getting this easy:
$oDomain = Get-ADDomain
$DeletedObjects = $oDomain.DeletedObjectsContainer
$UsersContainer = $oDomain.UsersContainer
These three lines got rid of about 30 lines of code between version 1 and version 2. But how can you get the information for another domain? The simple answer for all Active Directory commands for Windows PowerShell, is to use the -Server switch with the DNSDomain name instead of a true server name, for example:
$oDomain = Get-ADDomain –server corp.contoso.com
Undelete-User.ps1 lets you select the domain to search, and the destination for the restored user object. It defaults to running in test mode, where the changes are not committed to Active Directory. The maximum age of deleted objects is found inside the Configuration partition of the root forest domain. I capture that number with this line:
$MaxAge =Get-ADObject -Identity "CN=Directory Service,CN=Windows NT,CN=Services,CN=Configuration,$rootforestdomain"`
-Partition "CN=Configuration,$rootforestdomain" -Properties msDS-DeletedObjectLifetime |
select -ExpandProperty msDS-DeletedObjectLifetime
The script prompts you to choose how many days to go back in your search with $MaxAge as the default. You can choose to restore a single user by specifying the account SamAccountName. If you choose another domain, you are prompted to choose the global catalog server closest to you with the following:
$DomainGCs = Get-ADDomainController -server $strDomain `
-Filter {(Enabled -eq $true) -and (IsGlobalCatalog -eq $true)}
$strServer = $DomainGCs| select -Property hostname | out-gridview -Title "Select closest GC" -PassThru
When using Out-Gridview, it is important to remember to add –PassThru. Without it, nothing is returned by the cmdlet.
The query has been designed to work as fast as possible:
$users = Get-ADObject -searchbase $searchbase -Server $strServer -resultpagesize 100 `
-Filter {(whenChanged -ge $changedDate) -and (Deleted -eq $true) -and (SamAccountName -like $strUser) } `
-includeDeletedObjects -properties DisplayName, Description, SamAccountName, userprincipalname, DistinguishedName, WhenChanged
The default for $strUser is the asterisk ( * ). Because the user name is matched with the like operator, you can search for a single SamAccountName, or a list of names, such as SamMoor*. If you do nothing, the default applies and all accounts that were deleted during the time period are returned.
Instead of writing a log, I decided to use Start-Transcript and Stop-Transcript. Because transcription does not work inside the ISE, the script only offers a transcript when you are outside the ISE. The detection works like this:
if ( ($host.Name).Contains("ISE") -eq $False){
$retval = [System.Windows.Forms.MessageBox]::Show("Would you like a transaction log of script activity?",`
"Log", "YesNoCancel” , "Question” , "Button2")
The transcript logfile is sent by default the admin’s desktop. Note that I formatted the date inside of the conversion using ToString. Then the InputBox displays the log as the default:
$strLog = "$env:UserProfile\Desktop\"+$(Get-Date).ToString("yyyyMMdd_HHmm")+"_UndeleteUserLog.txt"
$strLog = [Microsoft.VisualBasic.Interaction]::InputBox("Log Path", "Path",$strLog)
The complete Windows PowerShell script is uploaded on the Script Center Repository: PowerShell Undelete User Script. The script is extensively commented. Please be a kind, gentle reader. I have only been using Windows PowerShell for a year and a half.
And now, a word from our lawyers: Alan Kaplan is an employee of the U.S. Department of Veteran’s Affairs. Any opinion expressed herein are his own, and not necessarily that of the Department.
Thank you so much for doing this, Alan—and for taking the time to share your experience with us today.
Join me tomorrow when I will have more way cool Windows PowerShell stuff.
I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Ed Wilson, Microsoft Scripting Guy