Showing posts with label organizational unit. Show all posts
Showing posts with label organizational unit. Show all posts

23 February, 2014

Parse OU location from DistinguishedName - AD

This post is just a bit of breadcrumb of Powershell bits. I've got some scripts which run regularly and have to analyse 100 000+ AD objects. It can take hours to run them, so every bit of code that can make one iteration in the loop a couple of milliseconds quicker can pay significant dividends when running against many objects.

As I was looking through my 3 years old code, I noticed an ugly solution (we all do these things, don't we). I needed to get the OU location of each object, so I decided to take the DistinguishedName attribute and drop the name of the object from the beginning of string therefore I end up with the full LDAP formatted path of the object (could have taken the CanonicalName attibute in reverse order and replace '\' with 'cn=' or 'dn=' or 'ou=', but then I would have to lookup each of those elements to figure if they are OUs or containers...etc.)

Let's take an example, the dinstinguishedName of an object is "CN=DroidServer,OU=ChalmunsCantina,OU=MosEisley,DC=tatooine,DC=com", so the LDAP path of the object can be determined by dropping the first part of this string before the first comma which leaves us with: "OU=ChalmunsCantina,OU=MosEisley,DC=tatooine,DC=com".

First attempt - original code in my script

Easy, lets split the string based on commas, put the elements into an array and drop the first element, then join the elements into a string again (now without the cn=objectname piece):
 $distinguishedName = "CN=DroidServer,OU=ChalmunsCantina,OU=MosEisley,DC=tatooine,DC=com"  
 $arrDN = New-Object System.Collections.ArrayList  
 $tmparr = $distinguishedName.Split(",")  
 $tmparr | %{[void]$arrDN.add($_)}  
 $arrDN.RemoveAt(0)  
 $accLocation = [string]::join(",",$arrDN)  
 $accLocation  

This will take 96.5 milliseconds on my machine.
96 milliseconds, fair enough, it's quicker than me doing this on paper.

Second attempt

Let's get rid of the foreach-object (%) when adding elements to $tmpArr and use the .AddRange method of the ArrayList instead - this will just add all elements in one go instead of going through element by element:
 $distinguishedName = "CN=DroidServer,OU=ChalmunsCantina,OU=MosEisley,DC=tatooine,DC=com"  
 $arrDN = New-Object System.Collections.ArrayList  
 $tmparr = $distinguishedName.Split(",")  
 [void]$arrDN.addrange($tmparr)  
 $arrDN.RemoveAt(0)  
 $accLocation = [string]::join(",",$arrDN)  
 $accLocation  


25 milliseconds, not bad, 4 times quicker.
 

Third attempt

To see if it can be even quicker, we'll need to "thinking outside the box" and see if there's any simpler solution than working with arrays and instead do this in one step and drop the first bit of the string which we don't need.
It's not obvious in PowerShell because the -replace operator does not support the regular expressions which refer only to the first occurrence in a string. What we can do is make it drop all characters which are not commas and they are followed by a comma, that would make sure the "cn=computername," string is dropped and we end up with the full LDAP path of the object:
 $distinguishedName = "CN=DroidServer,OU=ChalmunsCantina,OU=MosEisley,DC=tatooine,DC=com"  
 $accLocation = $distinguishedName -creplace "^[^,]*,",""  
 $accLocation  

Explanation for the regex pattern:
  • ^       start of the string
  • [^,]*   match one or more non-comma characters
  • ,       match a comma character
 
0.4669 milliseconds!
200 times quicker than the first solution! With 100 000 objects, originally it takes 160 minutes (obviously in real life it will be less because of caching...etc.) and with the 3rd solution it should take a bit less than a minute. Maybe it can be quicker with some better trick, but I'm not greedy, I've shaved off ~2.5 hours runtime, it's good enough for me... for today...

t

01 November, 2012

Restore GPO links with PowerShell - Active Directory

I was talking to people on an AD workshop the other day - which was a quite useful workshop BTW - and realised that most trainings and workshops show you how to perform tasks, do troubleshooting or - in this case - perform steps to restore AD in a smaller scale and they don't give you knowledge and mindset on how to do it with 1000+ objects, servers...or whatever.
For example, restore an object, a GPO, cleanup a DC from the environment...etc. But what if I have 50 GPOs to restore and they were linked to 150 OUs? While you learn the GUI way on these workshops, there's no story about an easy option to restore those GPO links on a wider scale.

In fact, there is no way to restore GPO links as such at all. You could perform authoritative restore on all the OUs but it seems overkill to me. Moreover, you would still need to find out which OUs you would need to restore.

I had some spare time during a break on the workshop so I though I'd give it a go and see how I could copy GPO links back from a DC in a Lag site to OUs on a production DC using PowerShell.

Lab:
I had 2 Windows Server 2008 Domain Controllers in the lab, one was in a Lag site with replication restricted to a small window overnight. Lag DC or Lag site means the replication is restricted to a small time window so the DC is intentionally kept behind in the replication to have live data in the system in case accidental deletion or modification happens on objects.The other DC was the production one, where I "accidentally" deleted some GPOs.
I restored the GPOs from the LAG DC with authoritatiove restore, but obviously the links disappeared.
So I had 1 DC where I still had the original state of the GPO links, but I still didn't want to go through all of them on the UI of GPMC. I wanted a quick script which would:
  • Take a list of GPO GUIDs
  • Look-up which OUs had it linked on the Lag DC
  • Go to the Production DC and add these GUIDs back to the gpLink attribute of each OU
Here's what I did (obviously, there should be some error handling and logging in there which I'll leave with the reader for now):
# List of GPO GUIDs I want to search for and restore links
$gpoGUIDsToBeResotred = @("034E8907-6058-4A19-B312-AB2A0408EDE4", "2EC45E73-E6BB-4F39-A221-E40630015B45")
$lagDC = "LAGDC"# DC name where the GPO links still exist
$prodDC = "ProdDC"# DC name where I want to create the GPO links again

# going through all GPO GUIDs 
foreach($gpoGUID in $gpoGUIDsToBeResotred){

    # Searching for OUs which have the give GPO GUID in their gpLink attribute
    Get-ADOrganizationalUnit -server $lagDC -filt 'gplink -like "*$gpoGUID*"' | %{
       $ou = $tmplinks = $null

       # bind the same OU on the Production DC where we want to restore the GPO links
       $ou = Get-ADOrganizationalUnit -server $prodDC -filter 'DistinguishedName -eq $_.DistinguishedName' -prop gplink

      
# store the current content of the gpLink attribute - this is very important as we want to append the attribute, not overwrite
       $tmplinks = $ou.gplink

       # add the new content to the gplink attribute of the OU
       $ou.gplink = $tmplinks + "[LDAP://cn={$gpoGUID},cn=policies,cn=system,DC=litware,DC=com;0]"

      
# commit changes
       Set-ADOrganizationalUnit -Instance $ou
    }
}



To get the GUID of a GPO, you can add a command similar to the below to the beginning of the script (you can also look it up in GPMC):
PS C:\> $gpoGUID = (get-gpo "default domain policy").id.guid

List all OUs which have a particular GPO linked:
PS C:\> Get-ADOrganizationalUnit -server lagDC -filt 'gplink -like "*$gpoGUID*"' | ft


I think, all in all, people can be glad that after so many years Microsoft finally realised that they must start building tools of their core products - such as AD - around PowerShell and move away from providing just a UI. So I'm grateful that I can now go through all my PS scripts which utilise ADSI and replace it with a cmdlet from ActiveDirectory PS module :). Although, you still need a bit of thinking and scripting to make things work.
In this example, you would wonder why I didn't use a command from the GroupPolicy Powershell module to create the GPO links. Well, because there isn't such a command. So there's still way to go for MSFT, but it's a good start.

Use this example above carefully and always test in a QA environment.

Clipboard friendly code:

 # List of GPO GUIDS I want to search for and restore links  
 $gpoGUIDsToBeResotred = @("034E8907-6058-4A19-B312-AB2A0408EDE4", "2EC45E73-E6BB-4F39-A221-E40630015B45")  
 $lagDC = "LAGDC"      # DC name where the GPO links still exist  
 $prodDC = "ProdDC"     # DC name where I want to create the GPO links again  
    
 # going through all GPO GUIDs  
 foreach($gpoGUID in $gpoGUIDsToBeResotred){  
   
      # Searching for OUs which have the give GPO GUID in their gpLink attribute  
      Get-ADOrganizationalUnit -server $lagDC -filt 'gplink -like "*$gpoGUID*"' | %{  
           $ou = $tmplinks = $null  
             
           # bind the same OU on the Production DC where we want to restore the GPO links  
           $ou = Get-ADOrganizationalUnit -server $prodDC -filter 'DistinguishedName -eq $_.DistinguishedName' -prop gplink  
             
           # store the current content of the gpLink attribute - this is very important as we want to append the attribute, not overwrite  
           $tmplinks = $ou.gplink  
             
           # add the new content to the gplink attribute of the OU  
           $ou.gplink = $tmplinks + "[LDAP://cn={$gpoGUID},cn=policies,cn=system,DC=litware,DC=com;0]"  
             
           # commit changes  
           Set-ADOrganizationalUnit -Instance $ou  
      }  
 }  

May the Force...
t