Creating an AD Shell

Hello Again

This is my third post in the Active Directory and PowerShell series. In this post I want to talk about how to create your own pseudo-cmdlets to manage the AD. I`m going to talk about how to present scripts to PowerShell that behave in a similar way to Cmdlets, that then allow easy administration of the AD. You may well think I`m mad, but how cool would it be to do this in a shell:

 

 

PS C:\> get-dn benp

'LDAP://CN=benp,OU=Admins,DC=umpadom,DC=com'

PS C:\> enable-user benp

User: benp enabled

PS C:\> disable-user benp

User: benp disabled

PS C:\> Set-ADProp benp GivenName Ben

GivenName property set for user benp

PS C:\> Get-ADProp benp GivenName

Ben

 

Wait a minute! There is no native support for Active Directory that does this. What magic is this!

 

Well in this post I`m going to show you how to create these example pseudo-cmdlets, and show you that whilst they are mighty cool, there is nothing that difficult or clever about them.

 

Creating the Pseudo-Cmdlets

 

First thing I want to be clear about is that these commands are not real Cmdlets. They have not been added via a snapin, and do not behave in the same way as Cmdlets. The command “get-dn” is simply a function that calls a script, and passes in some arguments. The script then does some stuff and returns some text. It does not return an object! Cmdlets normally return objects – these don’t. However, we can use them to create a shell like interface to do common tasks.

 

The Script

 

Let’s take Enable-User as an example. Probably best you read my previous 2 posts before you look at this, might make it a bit easier to understand:

 

 

# Function to enable user

# Takes SAM Name

$Error.clear()

$ErrorActionPreference = "silentlycontinue"

function get-dn ($SAMName)

{

     $root = [ADSI]''

     $searcher = new-object System.DirectoryServices.DirectorySearcher($root)

     $searcher.filter = ("(sAMAccountName= $SAMName)")

     $user = $searcher.findall()

     if ($user.count -gt 1)

     {

           $count = 0

           foreach($i in $user)

           {

                write-host $count ": " $i.path

                $count = $count + 1

           }

           $selection = Read-Host "Please select item: "

           return $user[$selection].path

     }

     else

     {

           return $user[0].path

     }

}

$userdn = get-dn $Args[0]

$user = [ADSI] $userdn

$user.psbase.invokeset("AccountDisabled", "False")

$user.setinfo()

if ($error.count -ne 0)

{

     "Error enabling user: " + $Args[0]

     $error[0].exception

}

else

{

     "User: " + $Args[0] + " enabled"

}

 

 

Most of this code is the same as get-dn, which I explained in my previous post. There only difference is what we do to the AD, and a bit of basic error checking. Let’s go through what happens in the code once we have the path to the user object we want to manipulate:

 

 

$userdn = get-dn $Args[0]

$user = [ADSI] $userdn

$user.psbase.invokeset("AccountDisabled", "False")

$user.setinfo()

 

 

 

All we do is create an object with the user object path as a constructor. Once we have this we simply set the user property AccountDisabled to be False. Then we commit the change to the AD. Simple! I use the invokeset method here, because it’s really easy to use. If you use the ADSI provider its actually a bit harder than the .Net Framework method.

 

The error handling here is very basic. I might write a post on error handling soon. The basic algorithm for error handling is:

 

Clear the $error.count

Do Stuff

If the $error.count is greater than 0 (we had an error)

                Output anerror message

Else

                Output operation successful text

 

 

Turning the script to a Pseudo-Cmdlet

 

To make the script accessible from just typing “enable-user”, all we need to do is create a function that utilises that script. To create a function we use the new-item Cmdlet. Here’s the code I use:

 

 

new-item -path function:enable-user -value {& 'C:\scripts\enable-user.ps1' $args[0]}

 

 

There we go. An easy way to enable user accounts.

 

That function will be loaded in PowerShell until the instance of PowerShell closes. So how can we make the function persistent? Well there are 2 ways. The simple way is to add this line to your profile. The profile file can be found by typing $profile in the shell. That means that every time you load PowerShell the function will be loaded.

 

However, I don’t really want that. I want to be able to load a script with all these functions when I want to administer the AD. Therefore I collected all my function definitions into 1 file: ADSupport.ps1. This file contains this:

 

 

new-item -path function:get-dn -value {& 'C:\scripts\getuserdn.ps1' $args[0]}

new-item -path function:get-adprop -value {& 'C:\scripts\get-adprop.ps1' $args[0] $args[1]}

new-item -path function:set-adprop -value {& 'C:\scripts\set-adprop.ps1' $args[0] $args[1] $args[2]}

new-item -path function:disable-user -value {& 'C:\scripts\disable-user.ps1' $args[0]}

new-item -path function:enable-user -value {& 'C:\scripts\enable-user.ps1' $args[0]}

 

 

Problem: if I run .\adsupport.ps1 it does not import these functions. If you try it you’ll find that none the functions are available. This is because by default the script is executed in a different scope. We want it executed in the PowerShell scope and therefore we must use an extra full stop (period if you live across the atlantic).

 

. .\adsupport.ps1

 

Now all your functions will be available to use.

 

Other Scripts

 

Here is the code to get all the above functions working.

 

Get-DN

 

See previous post

 

Disable User

 

# Function to disable user

# Takes SAM Name

$error.psbase.clear()

$erroractionpreference = "silentlycontinue"

function get-dn ($SAMName)

{

     $root = [ADSI]''

     $searcher = new-object System.DirectoryServices.DirectorySearcher($root)

     $searcher.filter = ("(sAMAccountName= $SAMName)")

     $user = $searcher.findall()

     if ($user.count -gt 1)

     {

           $count = 0

           foreach($i in $user)

           {

                write-host $count ": " $i.path

                $count = $count + 1

           }

           $selection = Read-Host "Please select item: "

           return $user[$selection].path

     }

     else

     {

           return $user[0].path

     }

}

$userdn = get-dn $Args[0]

$user = [ADSI] $userdn

$user.psbase.invokeset("AccountDisabled", "True")

$user.setinfo()

if ($error.count -ne 0)

{

     "Error disabling user: " + $Args[0]

     $error[0].exception

}

else

{

     "User: " + $Args[0] + " disabled"

}

 

 

Get-ADProp

# Function to query AD Object Properties

# Takes SAM, Property

$Error.psbase.clear()

$ErrorActionPreference = "SilentlyContinue"

#Use the below line in the profile to assign to get-adprop

#new-item -path function:get-adprop -value {& 'C:\store\ps scripts\get-adprop.ps1' $args[0] $args[1]}

function get-dn ($SAMName)

{

     $root = [ADSI]''

     $searcher = new-object System.DirectoryServices.DirectorySearcher($root)

     $searcher.filter = ("(sAMAccountName= $SAMName)")

     $user = $searcher.findall()

     if ($user.count -gt 1)

     {

           $count = 0

           foreach($i in $user)

           {

                write-host $count ": " $i.path

                $count = $count + 1

           }

           $selection = Read-Host "Please select item: "

           return $user[$selection].path

     }

     else

     {

           return $user[0].path

     }

}

$username = get-dn $Args[0]

$user = [ADSI] $username

$user.Get($Args[1])

if ($error.count -ne 0)

{

     "Error getting property " + $Args[1] + " for " + $Args[0]

     $error[0].exception

}

 

Set-ADProp

 

# Function to set AD Object Properties

# Takes SAM, Property, Value

#Use the below line in the profile to assign to get-adprop

#new-item -path function:set-adprop -value {& 'C:\store\ps scripts\set-adprop.ps1' $args[0] $args[1] $args[2]}

function get-dn ($SAMName)

{

     $root = [ADSI]''

     $searcher = new-object System.DirectoryServices.DirectorySearcher($root)

     $searcher.filter = ("(sAMAccountName= $SAMName)")

     $user = $searcher.findall()

     if ($user.count -gt 1)

     {

           $count = 0

           foreach($i in $user)

           {

                write-host $count ": " $i.path

                $count = $count + 1

           }

           $selection = Read-Host "Please select item: "

           return $user[$selection].path

     }

     else

     {

           return $user[0].path

     }

}

$username = get-dn $Args[0]

$user = [ADSI] $username

$user.put($Args[1], $Args[2])

$user.setinfo()

if ($error.count -ne 0)

{

     "Error setting " + $Args[1]

     $error[0].exception

}

else

{

     $Args[1] + " property set for user" + $Args[0]

}

 

Well ladies and gents. Hope you enjoyed this. Coding errors on a post card please J

That is all

BenP

Comments

  • Anonymous
    January 01, 2003
    Ben you are the dogs whatits. This is just an awesome post on how to 1) Make ad admin simpler and 2)

  • Anonymous
    January 01, 2003
    Ladies + Gents This week I have had the privilege of working at TechEd 2007. I’ve been on the PowerShell

  • Anonymous
    July 17, 2007
    Wouldn't it just be much easier to place the functions in a directory and the simply add that to the path? In this way, you do not have to double the function/files. I have a subdir called library being part of my path, so I simply stick files in to that when I want them to be easy accessible. Per