Hey, Scripting Guy! Question

Hey Scripting Guy! I need to be able to move a group from one organizational unit to another. The problem is that our network has thousands of groups and organizational units. What I need is a way to easily find the group and the organizational unit. After the group and the organizational unit have been found, it would be great if the script would pass the path to the group and to the organizational unit to the code that is used to move the group. If it did that, it would be great because I would not need to copy and paste the path to the objects. Is this possible?

-- OS

 

 

 

Hey, Scripting Guy! Answer

Hello OS,

With Windows PowerShell it seems that most things you need to do on your computer are possible to automate. At times the question might be how badly you want to write the script, but generally there are few things that are impossible to do. Luckily, this is not one of the nearly impossible tasks. However, it does involve two separate tasks. The first task is searching Active Directory, and the second is moving objects in Active Directory. The thing that might be confusing is that neither of these scripts looks much like its VBScript counterpart. A VBScript that moves an object in Active Directory uses the movehere method, while a VBScript that searches for groups in Active Directory uses ActiveX Data Objects (ADO). In Windows PowerShell, the techniques that are used are a bit more straightforward.

I decided to write the SearchForGroupMoveToOU.ps1 script to illustrate how to search Active Directory for a group and then move it to a different organizational unit. The SearchForGroupMoveToOU.ps1 script uses command-line arguments to simplify the process of running the script. The complete SearchForGroupMoveToOU.ps1 script is seen here.

SearchForGroupMoveToOU.ps1

Param(
  [string]$group,
  [string]$ou,
  [switch]$help,
  [switch]$whatif
) #end param

Function Get-AdObject($class,$name)
{
 $ds= New-Object System.DirectoryServices.DirectorySearcher
 $ds.filter = "(&(objectcategory=$class)(name=$name))"
 $ds.findall()
} #end function Get-AdObject

Function Move-AdGroup($group, $ou)
{
 $de = $group.GetDirectoryEntry()
 $destination = $ou.path
 $de.psbase.MoveTo($destination)
} #end function Move-AdGroup

Function Get-ScriptHelp
{
  "SearchForGroupMoveToOu.ps1 -group testGroup1 -ou students"
  "SearchForGroupMoveToOu.ps1 -group testGroup1 -ou students -whatif"
} #end function Get-ScriptHelp

Function Get-Whatif($group,$ou)
{
  "WHATIF: Moving group $group to ou $ou"
} #end function Get-Whatif

# *** Entry Point to Script ***
if($help) { Get-ScriptHelp ; exit }
if(-not($group -and $ou)) { throw "missing group name or ou name" }
if($whatif) { Get-Whatif -group $group -ou $ou ; exit }

$groupDE = Get-AdObject -class group -name $group
$ouDE = Get-AdObject -class organizationalUnit -name $ou
Move-AdGroup -group $groupDE -ou $ouDE

To move a group to a different organizational unit, you can use the Active Directory Users and Computers Microsoft Management Console, which is seen here:

Image of the Active Directory Users and Computers MMC


If you have more than one group that needs to be moved, it makes sense to create a script to perform the task. The first thing that must be done in the SearchForGroupMoveToOU.ps1 script is to create the command-line parameters. This will allow you to run the script without needing to edit it. The script uses two strings: the name of the group and the name of the organizational unit to which the group will be moved. The help switched parameter is used to provide help for the script. The whatif switched parameter is used to indicate what the script will do before it moves the group to the destination organizational unit. The command-line parameters are seen here:

Param(
  [string]$group,
  [string]$ou,
  [switch]$help,
  [switch]$whatif
) #end param

The first function to create is the Get-AdObject function, which is used to search for an object by name. By searching for an object by name you do not need to know the exact location of an object. This is helpful in large networks where you could have hundreds or even thousands of organizational units. You might not always know which organizational unit an object resides in. The Get-AdObject function requires two input parameters. The first is the type of object to search for, and the second is the name of the object. In the SearchForGroupMoveToOU.ps1 script, the Get-AdObject function is used to search for organizational units and groups. But it could be used in other scripts to search for users, printers, folders, computers, or any other object you could store in Active Directory. The function declaration is seen here:

Function Get-AdObject($class,$name)
{

After you have created the parameters for the Get-AdObject function, it is time to create an instance of the System.DirectoryServices.DirectorySearcher .NET Framework class. The returned DirectorySearcher is stored in the $ds variable. The DirectorySearcher has an overloaded constructor, which means the DirectorySearcher can be created in many different configurations. These different constructors are seen in Table 1.

Table 1

Name

Description

 DirectorySearcher()

Initializes a new instance of the DirectorySearcher class with default values.

 DirectorySearcher(DirectoryEntry)

Initializes a new instance of the DirectorySearcher class using the specified search root.

 DirectorySearcher(String)

Initializes a new instance of the DirectorySearcher class with the specified search filter. 

 DirectorySearcher(DirectoryEntry, String)

Initializes a new instance of the DirectorySearcher class with the specified search root and search filter. 

 DirectorySearcher(String, String[ ])

Initializes a new instance of the DirectorySearcher class with the specified search filter and properties to retrieve. 

 DirectorySearcher(DirectoryEntry, String, String[ ])

Initializes a new instance of the DirectorySearcher class with the specified search root, search filter, and properties to retrieve.

 DirectorySearcher(String, String[ ], SearchScope)

Initializes a new instance of the DirectorySearcher class with the specified search filter, properties to retrieve, and search scope. 

 DirectorySearcher(DirectoryEntry, String, String[ ], SearchScope)

Initializes a new instance of the DirectorySearcher class with the specified search root, search filter, properties to retrieve, and search scope.

 

Because no constructor is supplied to the New-Object, the DirectorySearcher is created with default values. This means it will connect to the current domain, the filter will search for all objects, and the search scope is subtree (which means it will make a recursive search of the domain).  These default values are seen in Table 2.

Table 2

Property

Initial value

SearchRoot

A null reference (Nothing in Visual Basic)

Filter

"(objectClass=*)"

PropertiesToLoad

An empty StringCollection object

SearchScope

subtree

 

The code that creates the DirectorySearcher with the default values and stores it in the $ds variable is seen here:

$ds= New-Object System.DirectoryServices.DirectorySearcher

Because no filter was supplied when the DirectorySearcher was created, you now need to create a filter that will return only the specified object from Active Directory. To do this, you use the filter property of the DirectorySearcher object stored in the $ds variable. The filter itself it uses the LDAP Dialect search filter syntax. This syntax is very compact, but is also rather cryptic. The ampersand operator is placed at the beginning of the search filter. Each Active Directory attribute and value pair is placed inside its own parentheses. This is seen here:

 $ds.filter = "(&(objectcategory=$class)(name=$name))"

You can use two methods from the DirectorySearcher class to find objects in Active Directory. The first is the findone method and the second is the findall method. The methods do exactly what their names would suggest. You are interested in finding all objects that meet your search criteria. Hopefully there will be only one match, but it is best to be on the safe side and use the findall method. This is seen here:

 $ds.findall()
} #end function Get-AdObject

The Move-AdGroup function is used to move the group to the target organizational unit. It takes two parameters: group and ou. Because the $group variable and the $ou variable contain the search results from the Get-AdObject function, they contain SearchResult objects. To move the group, you will need a DirectoryEntry object for the group. And to obtain the DirectoryEntry object for the group, use the GetDirectoryEntry method that is available from the SearchResult object. The organizational unit will need to have an adsPath. To obtain the adsPath, use the path property from the SearchResult object. This is seen here:

Function Move-AdGroup($group, $ou)
{
 $de = $group.GetDirectoryEntry()
 $destination = $ou.path

After you have retrieved the required objects, you can now call the moveto method from the base object that is found under the DirectoryEntry object. To gain access to the methods from the base object, use the psbase property. This is seen here:

 $de.psbase.MoveTo($destination)
} #end function Move-AdGroup

The Get-ScriptHelp function does not do anything fancy. It is used to display two strings to the Windows PowerShell console that indicate how to use the SearchForGroupMoveToOU.ps1 script. This is seen here:

Function Get-ScriptHelp
{
  "SearchForGroupMoveToOu.ps1 -group testGroup1 -ou students"
  "SearchForGroupMoveToOu.ps1 -group testGroup1 -ou students -whatif"
} #end function Get-ScriptHelp

It is time to create the Get-Whatif function, which is used to display the command-line values that are supplied to the script. This is used to see what the script would do if it were run without the whatif parameter. This is seen here:

Image of Get-Whatif function


This function is seen here:

Function Get-Whatif($group,$ou)
{
  "WHATIF: Moving group $group to ou $ou"
} #end function Get-Whatif

The entry point to the script is used to parse the command-line parameters. The first thing to do is to look for the presence of the $help variable. If the $help variable exists, the script was run with the help parameter. The script will call the Get-ScriptHelp function and exit. This is seen here:

if($help) { Get-ScriptHelp ; exit }

To move a group, you need to know both the name of the group and the name of the destination organizational unit. If the group parameter and the ou parameter are not supplied, the script will throw an error. This is seen here:

if(-not($group -and $ou)) { throw "missing group name or ou name" }

If the $whatif variable exists, the script was run with the whatif switched parameter. This means the user of the script wants to see what the script will do with the given parameters. To do this, the Get-Whatif function is called. This is seen here:

if($whatif) { Get-Whatif -group $group -ou $ou ; exit }

To search for the group object and the organizational unit object, call the Get-AdObject function, and then specify the class of the object and the name of the object. This is seen here:

$groupDE = Get-AdObject -class group -name $group
$ouDE = Get-AdObject -class organizationalUnit -name $ou

The last thing that must be done is to call the Move-AdGroup function. The Move-AdGroup function receives the SearchResult objects for the group and the user. This is seen here:

Move-AdGroup -group $groupDE -ou $ouDE

Well, OS, we have successfully searched for a group in Active Directory and moved the group to a different organizational unit. We hope you have found the discussion both illuminating and enlightening. Join us tomorrow as we plunge into the virtual mail bag and select a few e-mails that require short answers. Yes, it is time once again for Quick-Hits Friday. If you would like to stay abreast of everything happening on the Script Center, follow us on Twitter or on Facebook. If you would like to hang out with fellow scripters, check out the Official Scripting Guys Forum. It is a great place to lurk and to learn about scripting. It is also an excellent place where you can ask questions and share information. Until tomorrow, peace.

 

Ed Wilson and Craig Liebendorfer, Scripting Guys