Thursday, February 25, 2010

The Power of -Split

I work with Active Directory everyday. Sometimes, when I'm exporting information, I want to be able to break down the distinguishedName property to just give me the actual name. For example, in our environment we create names in AD with LastName, FirstName. The distguishedName for a user name like that ends up looking like this:

cn=Doe\, Jane,ou=users,ou=location,dc=example,dc=com

If I want to get just the name without the escape character or all the extra path info that makes up a distinguished name without PowerShell I'm probably going to copy the whole name to an editor and manually remove everthing to leave just Doe, Jane (probably just easier to type Doe, Jane instead of all of that copying stuff). But what happens if I need to get just the LastName, FirstName of a thousand people. Now I've some problems. I will have to export the DN's from AD, copy them to Word or Excel and then do a bunch of replacements or string manipulation on them.

Or I can write a PowerShell script to give them to me:


function Get-NameFromDN([string]$DN){            
    return ($DN.replace('\','') -split ",*..=")[1]            
}             
            
Get-QADObject | %{Get-NameFromDN $_.distinguishedName}            


Did you notice that I can split the string with a [regex] delimiter?  That is really cool.  Try the split yourself; paste the string below into a PowerShell prompt and hit enter:


"cn=mygroupobjectname,ou=security groups,ou=location,dc=example,dc=com" -split ",*..="


That produces:

PS C:>"cn=mygroupobjectname,ou=security groups,ou=location,dc=example,dc=com" -split ",*..="            
            
            
mygroupobjectname            
security groups            
location            
example            
com            


The [regex] expression ",*..=" says find a pattern that matches zero or more commas any two characters and an equal sign.  That would match any of the following:
cn=
,ou=
,dc=

This pattern becomes my delimiter so that when the split function runs it will split the string where ever it finds the match.  It puts the remaining elements in a zero based array.  Because a DN starts with a pattern match the first element of the array [0] will be empty.  That is why in my Get-NameFromDN function above I retrieve element [1].

PowerShell has a nice help article that explains the full functionality of -split.  I recommend that you read it to see just how powerful -split is.  You can find the help article by issuing the command:

Get-Help about_split

I used this example to show the power of the split operation, but you might be wondering just how practical this is.  Well as it turns out I have a scheduled tasks that runs every week that does a csvde export of ActiveDirectory.  We keep the csv files around for several months just in case we delete a user and need to recreate it we have all the info we need for the recreation.

When you delete a user account all of the group memberships are deleted.  So even if you use tools like ADFind and ADMod or Hyena to restore the account in AD, you will still need to get a list of groups the account belonged to from somewhere.  If you haven't backed up AD you need to query the user and they probably wont know and the whole process becomes time consuming.  That is why I do a csvde export for every domain I manage.  The csvde export creates a column in the csv file called memberof and in that column concatenates the distinguishedNames of all the non-principal groups the account belongs to with a semi-colon.  You can't just copy and paste the groups from the export into ADUC to add the groups, because, as mentioned above, ADUC's add group dialog won't accept distinguished names.  You could create a script to use the DN which would be fine.  But what if, like me, you needed to just supply the groups to your Logical Access team and they don't have any scripting ability.  Well you can run a script like this:


function Get-NameFromDN([string]$DN){            
    return ($DN.replace('\','') -split ",*..=")[1]            
}             
$lastname = ""            
            
<#The following produces a list of group DNs that are found in the csv for the users with the last name specified.  
To narrow your search you could -match on something else like 'lastname\, firstname' or a whole different column altogether.  
#>            
Import-Csv .\adexport.csv | ?{$_.DN -match 'lastname' -and $_.objectclass -eq 'user'} | %{$_.memberof.split(';')}            
            
<#However, you can't just copy the output, and send it to the LogicalAccess admin to paste in ADUC.  ADUC's add group 
dialog won't accept DN's.  So add the function at the end and now you get a list of names that you can copy and paste in 
an email to your LogicalAccess admin for him/her to create the account with.
#>            
Import-Csv .\adexport.csv | ?{$_.DN -match 'lastname' -and $_.objectclass -eq 'user'} | %{$_.memberof.split(';')} | %{Get-NameFromDN $_}             

The first Import-Csv produces a list of group DNs.  I can't use that for ADUC.

The second Import-Csv produces what I want.  Here is what it does.

The second Import-Csv line passes each line of the csv file to the pipeline where a match is performed in both the DN column and the objectclass column.  Once a match is found it grabs the memberof field in the matched line and splits it based on the semi-colon as the delimiter which produces a [string[]] of distinguished names of all the groups the account belonged to.  This array is passed to the pipeline and each DN is passed to the function Get-NameFromDN which uses the powerful -split operation to split the string based on a pattern ",*..=" (which will match ou= or ,ou= or cn= or ,dc= etc...).  The function then returns the second element of the split distinguished name (remember that because a DN starts with my delimiter pattern the first element [0] is empty).  The output produces a list of Group names only, that I pasted into an email and sent to my logical access guy.
I could have piped the group DNs to another function like Get-QADGroup $_, wrapped the function with parentheses, and gotten the name property like this:


...| %{(Get-QADGroup $_).name}

but that was significantly slower so I'd rather run it through my splitting function for speed sake.

You may have noticed in my Get-NameFromDN function I used the replace method.  That is because in my environment we use Lastname, Firstname.  LDAP has to escape the comma in the DN because each path element in the DN is separated by a comma; so commas within an attribute are escaped with a slash (\).  Additionally, csvde escapes the \, again so that it is \\, in the csv file.  The replace method in the function is replacing the escape character with nothing.  That way the list that is produced can be directly used in ADUC.

Of course this is just one example of how you can use the split functionality in PowerShell.  I'm sure that as you do more scripting the knowledge of the power of -split will come in handy.

Have fun.

No comments:

Post a Comment