July'06

PowerShell Active Directory Series

In my 200th post already (as I did just see) as promised here the start of the Active Directory Management series.In this first Part I will show how to make a Active Directory Object in Powershell,
connect to a domain, list Properties and Methods of the Object, and how to get the child Objects and store them in a Variable again.We will do all of this interactive from the commandline,

there are no CMDlets yet for Active directory management in PowerShell for version 1,
but as we can use .NET objects from powershell we are still able to manage Activedirectory from Powershell.

the .NET Classes we need are in the namespace system.directoryservices..
(This naming is because as you can connect not only to AD but to an other directoryservices using LDAP as well. )
in .net 2.0 we also have a activedirectory namespace for infrastructure management but more about that later in the Series.

The object we are going to use for this is called a Directoryentry.

So let’s start by making a connection to Active Directory. by creating an DirectoryEntry object,
as you might have seen before, to get a .NET object we need the command new-object :

PoSH> New-Object System.DirectoryServices.DirectoryEntrydistinguishedName                                                                                                     
—————–                                                                                                     
{DC=mow,DC=local}
and as you can see as a result we get back the distinguishedName of the domain,there are 2 things to note here,

you can see that the directoryentry constructed without parameters defaults to the default naming context, that is the current domain, in this case mow.local .

second thing to note is that as we just throw the object to the pipeline, so it gets the default formatting and after that its gone.

as I want to use it a bit more, I will put it into a variable so we can keep using it. I just use the arrow-up to get the previous line again, and Home to get to the beginning of the line and type ‘[variablename] = ‘. in front of it.
I like to work this way to first check if I get back the object, and then if it is OK I put it into a variable, otherwise I need to check the variable everytime to see if the command succeeded.

as this is connecting us to the root namespace of the domain, I will call the variable $root.PoSH>$root = New-Object System.DirectoryServices.DirectoryEntry
PoSH>$root
distinguishedName                                                                                                     
—————–                                                                                                     
{DC=mow,DC=local}
You can see we now have the Object in a Variable and can start using itnext, as the default formatter only displays the distinguishedName of the directoryentry object.

I use Format-List * ( by the Alias FL )to show all properties.# Get all Properties $root | fl *you can also get the Methods and Properties of the Object by Using Get-Member :
so next lets ask the directoryEntry Object what more it can do :

# Get All Members $root  | get-Member

# Or to get only the Methods :

PoSH> $root | get-member -MemberType method

   TypeName: System.DirectoryServices.DirectoryEntry

Name                      MemberType Definition                                                                       
—-                      ———- ———-                                                                       
add_Disposed              Method     System.Void add_Disposed(EventHandler value)                                     
Close                     Method     System.Void Close()                                                              
CommitChanges             Method     System.Void CommitChanges()                                                      
CopyTo                    Method     System.DirectoryServices.DirectoryEntry CopyTo(DirectoryEntry newParent), Syste…
CreateObjRef              Method     System.Runtime.Remoting.ObjRef CreateObjRef(Type requestedType)                  
DeleteTree                Method     System.Void DeleteTree()                                                         
Dispose                   Method     System.Void Dispose()                                                            
Equals                    Method     System.Boolean Equals(Object obj)                                                
get_AuthenticationType    Method     System.DirectoryServices.AuthenticationTypes get_AuthenticationType()            
get_Children              Method     System.DirectoryServices.DirectoryEntries get_Children() 
…..
…..
and hmmm, this get_children method looks handy,
let’s try : $root.get_Children()
PoSH> $root.get_Children()distinguishedName                                                                                                     
—————–                                                                                                     
{CN=Builtin,DC=mow,DC=local}                                                                                          
{CN=Computers,DC=mow,DC=local}                                                                                        
{OU=Domain Controllers,DC=mow,DC=local}                                                                               
{CN=ForeignSecurityPrincipals,DC=mow,DC=local}                                                                        
{CN=Infrastructure,DC=mow,DC=local}                                                                                   
{CN=LostAndFound,DC=mow,DC=local}                                                                                     
{OU=MowOu,DC=mow,DC=local}                                                                                            
{CN=NTDS Quotas,DC=mow,DC=local}                                                                                      
{CN=Program Data,DC=mow,DC=local}                                                                                     
{CN=System,DC=mow,DC=local}                                                                                           
{CN=Users,DC=mow,DC=local}

Nice just what I needed as there is not much to do in the root of the ActiveDirectory, so lets look if we can connect to one of the SubOU’s
You would expext that you can just use the indexer to get to the wanted OU from the Array returned like this :
PoSH> $root.get_Children()[6]
Unable to index into an object of type System.DirectoryServices.DirectoryEntries.
At line:1 char:22
This does not work, you can see the reason for this in the Errormessage,
we did not get back a collection of DirectoryEntry Objects, but one instance of another .NET class : System.DirectoryServices.DirectoryEntries
You can also check on this by asking it for it’s type :

# Check the type $root.get_Children().GetType()Note that normaly GM (Get_member) will not show this as it does enumerate the Object ( ?? and you just said that it was not a collection.. nope it is not and it has no indexer but it has a GetEnumerator() Method, so the pipeline of PowerShell does Enumerate on it, and get-member will show the members of the Items that got enumerated, but will show them only one for every type thrown to the pipeline)

PoSH> $root.get_Children() | gm   TypeName: System.DirectoryServices.DirectoryEntry

Name                      MemberType Definition                                                                       
—-                      ———- ———-                                                                       
add_Disposed              Method     System.Void add_Disposed(EventHandler value)                                     
Close                     Method     System.Void Close()                                                              
CommitChanges             Method     System.Void CommitChanges()
you can also give the object as a parameter to get-member to avoid this
Get-Member -inputObject $rootor a workaround I like is put a comma before the object (again as I can use “Arrow Up” to get the last line this change is much easier in interactive use when you need members of the collection not the instances):

,$root.get_Children() | gm   TypeName: System.DirectoryServices.DirectoryEntries

Name             MemberType Definition                                                                                
—-             ———- ———-                                                                                
Add              Method     System.DirectoryServices.DirectoryEntry Add(String name, String schemaClassName)          
Equals           Method     System.Boolean Equals(Object obj)                                                         
Find             Method     System.DirectoryServices.DirectoryEntry Find(String name), System.DirectoryServices.Dire…
get_SchemaFilter Method     System.DirectoryServices.SchemaNameCollection get_SchemaFilter()                          
GetEnumerator    Method     System.Collections.IEnumerator GetEnumerator()                                            
GetHashCode      Method     System.Int32 GetHashCode()                                                                
GetType          Method     System.Type GetType()                                                                     
Remove           Method     System.Void Remove(DirectoryEntry entry)                                                  
ToString         Method     System.String ToString()                                                                  
SchemaFilter     Property   System.DirectoryServices.SchemaNameCollection SchemaFilter {get;} 
Now we can see its not an array, but we can workaround this lack of an by explicitly making it an array by casting it like this :@($root.get_Children())[6]distinguishedName                                                                                                     
—————–                                                                                                     
{OU=MowOu,DC=mow,DC=local}

but as you also might have seen in the get-member output, the directoryentries class also has a find method, that has a name as a parameter and.
that will find and return a specific child
$root.get_Children().find(‘ou=mowOu’)distinguishedName                                                                                                     
—————–                                                                                                     
{OU=MowOu,DC=mow,DC=local}

# and store in a variable again for later usage

$mowOU = $root.get_Children().find(‘ou=mowOu’)

# and list all properties again.

Posh> $MowOu | fl *

objectClass          : {top, organizationalUnit}
ou                   : {MowOu}
distinguishedName    : {OU=MowOu,DC=mow,DC=local}
instanceType         : {4}
whenCreated          : {5/18/2006 7:15:13 PM}
whenChanged          : {5/18/2006 7:15:13 PM}
uSNCreated           : {System.__ComObject}
uSNChanged           : {System.__ComObject}
name                 : {MowOu}
objectGUID           : {139 153 183 252 115 3 24 73 145 12 14 36 64 30 237 202}
objectCategory       : {CN=Organizational-Unit,CN=Schema,CN=Configuration,DC=mow,DC=local}
nTSecurityDescriptor : {System.__ComObject}
note that this is also a DirectoryEntry Object but it will have other properties as it as a OU Object not the domain.and from here we can use the some method to list the objects (Users / Computers / Sub-OU’s)

$mowou.get_children()# and for example get a user into a variable to edit it etc :

$mow = $mowou.get_children().find(‘cn=mow’) note that this is not the only way to get to the sub-OU, If you know the LDAP Path you can pass it in the constructor and connect to the OU directly :
(also note that you can leave away the system namespace as PowerShell will append it)
# connect directly to an LDAP path :$mowOu = New-Object DirectoryServices.DirectoryEntry(“LDAP://OU=MowOu,DC=mow,DC=local”)

In the next part of this series we will create a User, and see how to create a loop to make more as 1 user at a time,
and fill some more properties.
later in this series we will use the DirectorySearcher to do searches in AD

But for now by creating one .NET Object and only the 3 most basic PowerShell Cmdlets (new-object / format-(List/Table) / get-member ) we already can :

Connect to AD
List properties of AD Objects
List methods of AD Objects
Walk to the Domain structure to wanted OU
Connect directly to SubOU’s
List members of OU’s

and the only thing we have to remember is that we need a directoryServices.DirectoryEntry Object.
It will find out the domain / LDAP path for us if we do not know it, and we can use get-member to remember / learn us what the object can do.and to explore the objects we get back.

So as you see it is not as hard as i might seem being without Cmdlets.

More in next post, till then try walking to different parts of AD this way and look at Computer / Users Objects,and do Format-List * and get-Member on the returned objects, to get used to the DirectoryObject and learn for the output you get back,
(for a preview of the next part Creating a user as in the Demo, note the Add() method on the directoryEntries class)

In this second part of the PowerShell and Active Directory series, we go on from PowerShel and Active Directory Part 1 (Sorry for the Typo)
A quick refresh and commands needed to get back in that state :
# Connect to a Domain (Default Naming Context) :$root = New-Object System.DirectoryServices.DirectoryEntry

# listing the Child Objects

$root.get_Children()

# Getting a Child Object

$mowOU = $root.get_Children().find(‘ou=mowOu’)

# connect directly to a Active Directory Object using a path

$mowOu = New-Object DirectoryServices.DirectoryEntry(“LDAP://OU=MowOu,DC=mow,DC=local”)Also we did see that we can use format-list * / format-Table * to list all the properties,
and that we can get the members and Properties by using get-member.
also you could see that the DirectoryEntry Object is a Wrapper that can contain different kinds of AD Objects, e.g. : Domain,OU,User (and if you did follow my advice and did some “browsing” on your own you could see that that goes also for Groups and other objects). We learned that its always good to check the type of the object that we get back as sometimes it’s a bit different that that we expect, also we did see how to use get-member to get the methods of an object that is enumerable.
In this second part we are going to create a user.
and then create a 100 of them and set some more properties.

as in this part of the series we going to write to the ActiveDirectory it might not be a good Idea to do it in a Production environment, or you might not have an AD present and still want to follow the series, for the Demo I did use a VM with a domain controller but you can also use ADAM (AD application Mode) that you can install on your own workstation to test.

For more information about using adam and how to get and install it see this post : (link to Adam still works and looks updated to SP1)
Monad and Adam (and a preview of my to-be AD provider)

(PS nope I did not work on the AD provider anymore and it might take a while before I do, Sorry not enough insomnia time)also you can find an example to create the MowOU in that post, from there on you can follow the series, only the “automatic” connection to the Domain will not work, you need to give the LDAP path in the DirectoryEntry Constructor, as explained that post.

So after you connected to the Test OU of your choice, let’s go on Creating a User

As we have see in former post, the Get_Children() Method of Returns a DirectoryEntries Object, and as I hinted or you might have see in the get-member output this Object also has a Add Method, let’s find out how that does work :

PoSH>$mowoudistinguishedName
—————–
{OU=MowOu,DC=mow,DC=local}

PoSH>$mowou.get_Children().add

MemberType          : Method
OverloadDefinitions : {System.DirectoryServices.DirectoryEntry Add(String name, String schemaClassName)}
TypeNameOfValue     : System.Management.Automation.PSMethod
Value               : System.DirectoryServices.DirectoryEntry Add(String name, String schemaClassName)
Name                : Add
IsInstance          : True
You call a method in PowerShell with () behind it, if you leave them away as I did above you get some info about the Method,
especially the OverloadDefinitions is very valuable, in this case it shows us that the Add method has one overload and it takes a string name and a string SchemaClassName.
# Create UserPoSH>$user = $mowou.get_Children().add(“CN=Mow2″,‘User’)

PoSH>$user

distinguishedName
—————–
{}

PoSH>$mowou.get_Children().find(“CN=Mow2″)

Exception calling “Find” with “1” argument(s): “There is no such object on the server. (Exception from HRESULT: 0x80072030)”
At line:1 char:27
+ $mowou.get_Children().find( <<<< “CN=Mow2″)

# Save

PoSH>$user.CommitChanges()

PoSH>$user

distinguishedName
—————–
{CN=Mow2,OU=MowOu,DC=mow,DC=local}

PoSH>$mowou.get_Children().find(“CN=Mow2″)

distinguishedName
—————–
{CN=Mow2,OU=MowOu,DC=mow,DC=local}
Note that it absolutely needed that you use a variable here as the User Will not be created until you call the CommitChanges Method, you can see that from the first output that the DN name is not yet filled, and if you throw it to the pipeline you can not call the commintChanges() Method, that why you need the variable for this.
OK, OK not completely you can do it also like this ;-) :
# Make an OU 
PoSH>($mowou.get_Children().add(“OU=MowSubOu”,‘organizationalUnit’)).CommitChanges()
PoSH>$mowou.get_Children().find(“OU=MowSubOu”)
distinguishedName
—————–
{OU=MowSubOu,OU=MowOu,DC=mow,DC=local}

PoSH> For the example above you can let PowerShell first evaluate what is in the parents and then call a method on it, also you can see how to do the same for an OU.OK, Let’s do not one user (as they still can say they do it quicker in the MMC GUI, and to have some users to test on in later parts of this series), let’s do a 100 (for readability I did 5 here on the blog)and name them MowTest001 to MowTest100 and do not use a script but just go on from the commandline.

first lets count to 100, in PowerShell we have a nice range-operator “..” for this :
PoSH>1..5
1
2
3
4
5
Then we can use a pipeline and Foreach (by alias %) :

PoSH>1..5 |% {$_}
1
2
3
4
5
and then use a string and and the -F (format) operator in PowerShell, to format the numbers with trailing zeros and add them to a string, first the {0} inserts the first parameter, and then you can also use the .NET formatstrings to format them.Jeffrey Snover did also a BlogEntry about it on the PowerShell here :
Using Format Control Strings.

For another example (formating as Percent) and some more formatting tricks in PowerShell see this post.2006 Winter Scripting Games (part 2)

And here (as mentioned in that post also) you can find a good reference for formattingStrings in .NET :
(This is a different one from the one in Jeffrey Snovers’s post with some more info !)
Standard Numeric Format Strings

I had some problems with this in the Demo (I turned string and parameter arround) so it might have not been that clear
in the Demo, on the other hand it shows how you can play with a line till you have it right by using the up arrow.( and it was cool for a time to call out : Jeffrey Help ! and he DOES show up to help me out LOL ;-))
# First insert the Number :PoSH>1..5 |% {“MowTest{0}” -f $_}
MowTest1
MowTest2
MowTest3
MowTest4
MowTest5

# Format Number 

PoSH>1..5 |% {“MowTest{0:d3}” -f $_}
MowTest001
MowTest002
MowTest003
MowTest004
MowTest005
then if you get it right you can make a loop and use the History of your commandsettings to add former commands to the loop, it might be a bit hard to show on paper but the trick was to open the loop, and walk back with the up arrow in you history, edit add former commands you did to the loop, then close it with }[enter][enter] (you need a blank line after it).

# if you add another foreach and curly braces open and press enter you get a >> PromptPoSH>1..5 |% {“MowTest{0:d3}” -f $_} |% {
>>

# Walk with upArrow to former line :

PoSH>1..5 |% {“MowTest{0:d3}” -f $_} |% {
>> $user = $mowou.get_Children().add(“CN=Mow2″,‘User’)

# Change it, and use DownArrow to walk to the CommitChangesline from history.

PoSH>1..5 |% {“MowTest{0:d3}” -f $_} |% {
>> $user = $mowou.get_Children().add(“CN=$_”,‘User’)
>> $user.CommitChanges()
>> }
>>

# Now the items are created.

PoSH>$mowou.get_Children()

distinguishedName
—————–
{CN=$_,OU=MowOu,DC=mow,DC=local}
{CN=AWu0001,OU=MowOu,DC=mow,DC=local}
{CN=AWu0290,OU=MowOu,DC=mow,DC=local}
{CN=mow,OU=MowOu,DC=mow,DC=local}
{CN=Mow2,OU=MowOu,DC=mow,DC=local}
{OU=MowSubOu,OU=MowOu,DC=mow,DC=local}
{CN=MowTest001,OU=MowOu,DC=mow,DC=local}
{CN=MowTest002,OU=MowOu,DC=mow,DC=local}
{CN=MowTest003,OU=MowOu,DC=mow,DC=local}
{CN=MowTest004,OU=MowOu,DC=mow,DC=local}
{CN=MowTest005,OU=MowOu,DC=mow,DC=local}
{CN=test,OU=MowOu,DC=mow,DC=local}

B.T.W. if you where at the demo from this output you can get what went wrong the first time trying to create them :
I made the first user like this :
$user = $mowou.get_Children().add(‘CN=Mow2′,’User’)

working correct at the time, but not when I did change it to $mowou.get_Children().add(‘CN=$_’,’User’) in the loop.

What went wrong ?

As we did create 100 Users, but there is a bit more work to be done (filling properties and Enable them for example),
I will get on to that in the next post, and to format and list the created objects, also You might have noticed the examples in the Adam post where a bit different, I will explain why also in the next post, after that we will look at taking import for SQL or CSV for example and searching AD.

This is the third part in the PowerShell Active directory series,in this part we are going to do the rest of the work to completely configure a User.Part 1 and 2 you can find here :

PowerShel and Active Directory Part 1
PowerShell and Active Directory Part 2

We did create a bounce of users last time, but as I did say in last post we are not really ready yet with the users created, as we want also to fill in some more properties and enable them, you can take a user created in last post or as here start with a Fresh one.# Make a new User called Test$mowOu = New-Object DirectoryServices.DirectoryEntry(“LDAP://OU=MowOu,DC=mow,DC=local”)
$user = $mowOu.get_Children().Add(“CN=Test”,‘user’)
$user.CommitChanges()
 
# Or use an existing User :

PoSH>$user = $mowou.get_Children().find(‘cn=test’)
PoSH>$user
distinguishedName
—————–
{CN=test,OU=MowOu,DC=mow,DC=local}

# Or directly again :

$User = New-Object DirectoryServices.DirectoryEntry(“LDAP://cn=test,OU=MowOu,DC=mow,DC=local”)

# Remove it and make a new one

PoSH>$mowou.get_Children().remove($user)
PoSH>$user = $mowou.get_Children().add(“cn=Test”,‘user’)
PoSH>$user.CommitChanges()

# list all the properties :

PoSH>$user | fl *

objectClass          : {top, person, organizationalPerson, user}
cn                   : {Test}
distinguishedName    : {CN=Test,OU=MowOu,DC=mow,DC=local}
instanceType         : {4}
whenCreated          : {7/2/2006 2:10:58 PM}
whenChanged          : {7/2/2006 2:10:58 PM}
uSNCreated           : {System.__ComObject}
uSNChanged           : {System.__ComObject}
name                 : {Test}
objectGUID           : {4.0.208.232.208.190.162.64.184.228.97.225.109.37.99.125}
userAccountControl   : {546}
badPwdCount          : {0}
codePage             : {0}
countryCode          : {0}
badPasswordTime      : {System.__ComObject}
lastLogoff           : {System.__ComObject}
lastLogon            : {System.__ComObject}
pwdLastSet           : {System.__ComObject}
primaryGroupID       : {513}
objectSid            : {1.5.0.0.0.0.0.5.21.0.0.0.94.172.186.232.167.29.117.70.12.60.36.84.61.12.0.0}
accountExpires       : {System.__ComObject}
logonCount           : {0}
sAMAccountName       : {$T13000-LM8U3IDDHKKL}
sAMAccountType       : {805306368}
objectCategory       : {CN=Person,CN=Schema,CN=Configuration,DC=mow,DC=local}
nTSecurityDescriptor : {System.__ComObject}
After this refresh of things done in former posts and now we have a situation with a Fresh User again, we can go further adding the properties to configure the User compleetly, let’s start with that SAMAccountName that is auto generated,sAMAccountName : {$T13000-LM8U3IDDHKKL}

as we still do need it a lot in day to day work and such a generated name is not handy, so we want it to be the same as the CN lets rename it :
(note this first part does not work for ADAM users they need to use the Native Methods here, more info later in this post.).# Change sAMAccountNamePoSH>$user.sAMAccountName = ‘test’
PoSH>$user.sAMAccountName
test

Note that we do not have to call Commit Changes here, this will be done automatically the .NET DirectoryEntry wrapper-object will handle this.Now Lets start with adding some more properties :

# Add a Property that does not exist (AD only)$user.Department = ‘foo’

PoSH>$user | fl *

objectClass          : {top, person, organizationalPerson, user}
cn                   : {Test}
distinguishedName    : {CN=Test,OU=MowOu,DC=mow,DC=local}
instanceType         : {4}
whenCreated          : {7/2/2006 2:10:58 PM}
whenChanged          : {7/2/2006 2:12:34 PM}
uSNCreated           : {System.__ComObject}
uSNChanged           : {System.__ComObject}
department           : {Foo}
……
Now you can see that, even when the property was not there before, It just got created and The Object saved again.Now as I noted before the examples above, these will not work for ADAM users, you can still do it, but need to use Invoke or InvokeSet here as I will show below.:

# ADAM examplePoSH>$u.department = ‘foo’
Exception setting “department”: “The specified directory service attribute or value does not

exist. (Exception from HRESULT: 0x8007200A)”
At line:1 char:4
+ $u.d <<<< epartment = ‘foo’

# Using Invoke

PoSH>$u.department
PoSH>$u.InvokeSet(‘department’,‘foo’)
PoSH>$u | fl *

objectClass          : {top, person, organizationalPerson, user}
cn                   : {test}
distinguishedName    : {CN=test,OU=MowOu,DC=mow,DC=adam}
instanceType         : {4}
whenCreated          : {6/28/2006 7:45:25 PM}
whenChanged          : {6/28/2006 7:45:25 PM}
uSNCreated           : {System.__ComObject}
uSNChanged           : {System.__ComObject}
name                 : {test}
objectGUID           : {0.18.124.80.174.38.202.68.189.94.92.39.181.158.201.85}
badPwdCount          : {0}
badPasswordTime      : {System.__ComObject}
pwdLastSet           : {System.__ComObject}
objectSid            :

{1.5.0.0.18.108.99.8.192.67.92.229.143.126.164.172.231.8.44.75.160.22.205.75.212.176.144.138}
objectCategory       : {CN=Person,CN=Schema,CN=Configuration,CN={95B8575E-7CB6-4F6F-8F90-

85B1EBE795FF}}
nTSecurityDescriptor : {System.__ComObject}

# We have to Manualy save here :

PoSH>$u.CommitChanges()
PoSH>$u | fl *

department           : {foo}
objectClass          : {top, person, organizationalPerson, user}
cn                   : {test}
distinguishedName    : {CN=test,OU=MowOu,DC=mow,DC=adam}
instanceType         : {4}
whenCreated          : {6/28/2006 7:45:25 PM}
whenChanged          : {6/28/2006 10:02:26 PM}
uSNCreated           : {System.__ComObject}
uSNChanged           : {System.__ComObject}
name                 : {test}
objectGUID           : {0.18.124.80.174.38.202.68.189.94.92.39.181.158.201.85}
badPwdCount          : {0}
badPasswordTime      : {System.__ComObject}
pwdLastSet           : {System.__ComObject}
objectSid            :

{1.5.0.0.18.108.99.8.192.67.92.229.143.126.164.172.231.8.44.75.160.22.205.75.212.176.144.138}
objectCategory       : {CN=Person,CN=Schema,CN=Configuration,CN={95B8575E-7CB6-4F6F-8F90-

85B1EBE795FF}}
nTSecurityDescriptor : {System.__ComObject}

# Using invoke

PoSH>$u.invoke(“Put”,(“displayName”,“test”))
PoSH>$u.CommitChanges()
PoSH>$u.displayName
test

# after it is created we can directly change it in ADAM

PoSH>$u.displayName = ‘Testing’
PoSH>$u.displayName
Testing
This also works in an Active Directory environment ofcourse and as we shall see later in this post, there are also uses for those InvokeGet and InvokeSet methods in Active Directory use. Also when we Enable the account and set terminal serversettings, but first lets find out what properties are available.As we are busy with a User let’s find out what properties are available. We will look this up in the Active Directory Schema.For this we need another .NET class, now from the System.DirectoryServices.ActiveDirectory NameSpace the ActiveDirectorySchema Class, and as a lot of the classes in the ActiveDirectory Namespace do, it does not have a constructor (see outputbelow), we have to use a static method .# does not work :PoSH>New-Object System.DirectoryServices.ActiveDirectory.ActiveDirectorySchema
New-Object : Constructor not found. Cannot find an appropriate constructor for type

System.DirectoryServices.ActiveDirectory.ActiveDirectorySchema.
At line:1 char:11
+ New-Object  <<<< System.DirectoryServices.ActiveDirectory.ActiveDirectorySchema

# it has no constuctors :

PoSH>[System.DirectoryServices.ActiveDirectory.ActiveDirectorySchema].GetConstructors() 

# listing the static Methods :

PoSH>[System.DirectoryServices.ActiveDirectory.ActiveDirectorySchema] | gm -static

   TypeName: System.DirectoryServices.ActiveDirectory.ActiveDirectorySchema

Name             MemberType Definition
—-             ———- ———-
Equals           Method     static System.Boolean Equals(Object objA, Object objB)
GetCurrentSchema Method     static System.DirectoryServices.ActiveDirectory.ActiveDirectorySchema GetCurrentSchema()
GetSchema        Method     static System.DirectoryServices.ActiveDirectory.ActiveDirectorySchema GetSchema(DirectoryContextcontext)
ReferenceEquals  Method     static System.Boolean ReferenceEquals(Object objA, Object objB)

# using the static GetCurrentSchema() method

$schema = [System.DirectoryServices.ActiveDirectory.ActiveDirectorySchema]::GetCurrentSchema()Note that static methods get called with ::,and as you use get-member without the -static switch you get the Methods of the RunTime Object, (for example the get_constructors Method is very handy, but more about that in later post)
also note that this is a long type, for that I made an extension to the Tabcompletion funtion that makes it a lot easier to work with .net types directly from powershell as you could see in the Demo, you can find it here (http://mow001.blogspot.com/2006/06/powershell-tab-completion-part-4.html)
but still making a function for it is more easy, so lets turn this in to a small function :
# Shortcut function for getting Schema Objectfunction get-Schema [System.DirectoryServices.ActiveDirectory.ActiveDirectorySchema]::GetCurrentSchema()}

# Get the Schema

$schema = get-Schema

# ask what it can do for us .

PoSH>$Schema | gm

   TypeName: System.DirectoryServices.ActiveDirectory.ActiveDirectorySchema

Name                     MemberType Definition
—-                     ———- ———-
Dispose                  Method     System.Void Dispose()
Equals                   Method     System.Boolean Equals(Object obj)
FindAllClasses           Method     System.DirectoryServices.ActiveDirectory.ReadOnlyActiveDirectorySchemaClassCollectio
FindAllDefunctClasses    Method     System.DirectoryServices.ActiveDirectory.ReadOnlyActiveDirectorySchemaClassCollectio
FindAllDefunctProperties Method     System.DirectoryServices.ActiveDirectory.ReadOnlyActiveDirectorySchemaPropertyCollec
FindAllProperties        Method     System.DirectoryServices.ActiveDirectory.ReadOnlyActiveDirectorySchemaPropertyCollec
FindClass                Method     System.DirectoryServices.ActiveDirectory.ActiveDirectorySchemaClass FindClass(String
FindDefunctClass         Method     System.DirectoryServices.ActiveDirectory.ActiveDirectorySchemaClass FindDefunctClass
FindDefunctProperty      Method     System.DirectoryServices.ActiveDirectory.ActiveDirectorySchemaProperty FindDefunctPr
FindProperty             Method     System.DirectoryServices.ActiveDirectory.ActiveDirectorySchemaProperty FindProperty(
get_Name                 Method     System.String get_Name()
get_SchemaRoleOwner      Method     System.DirectoryServices.ActiveDirectory.DirectoryServer get_SchemaRoleOwner()
GetDirectoryEntry        Method     System.DirectoryServices.DirectoryEntry GetDirectoryEntry()
GetHashCode              Method     System.Int32 GetHashCode()
GetType                  Method     System.Type GetType()
RefreshSchema            Method     System.Void RefreshSchema()
ToString                 Method     System.String ToString()
Name                     Property   System.String Name {get;}
SchemaRoleOwner          Property   System.DirectoryServices.ActiveDirectory.DirectoryServer SchemaRoleOwner {get;}

# Getting the Schema of a user :

PoSH>$SchemaUser = $schema.FindClass(‘user’)
PoSH>$SchemaUser.get_MandatoryProperties() | fl name

Name : cn

Name : instanceType

Name : nTSecurityDescriptor

Name : objectCategory

Name : objectClass

Name : objectSid

Name : sAMAccountName

# Get All Optional Properties of the User Class

PoSH>$SchemaUser.get_OptionalProperties() | ft name,Syntax,IsSingleValued,IsIndexed,IsInGlobalCatalog

Name                                     Syntax          IsSingleValued               IsIndexed       IsInGlobalCatalog
—-                                     ——          ————–               ———       —————–
accountExpires                            Int64                    True                   False                   False
accountNameHistory              DirectoryString                   False                   False                   False
aCSPolicyName                   DirectoryString                    True                   False                   False
adminCount                                  Int                    True                   False                   False
adminDescription                DirectoryString                    True                   False                   False
adminDisplayName                DirectoryString                    True                   False                   False
allowedAttributes                           Oid                   False                   False                   False
allowedAttributesEff…                     Oid                   False                   False                   False
allowedChildClasses                         Oid                   False                   False                   False
allowedChildClassesE…                     Oid                   False                   False                   False
altSecurityIdentities           DirectoryString                   False                    True                    True
assistant                                    DN                    True                   False                   False
attributeCertificate…             OctetString                   False                   False                   False
audio                               OctetString                   False                   False                   False
…..

# Get Only Properties in Global Catalog :

PoSH>$SchemaUser.get_OptionalProperties() |? {$_.IsInGlobalCatalog}| ft name,Syntax,IsSingleValued,IsIndexed,IsInGlobalC
atalog

Name                                     Syntax          IsSingleValued               IsIndexed       IsInGlobalCatalog
—-                                     ——          ————–               ———       —————–
altSecurityIdentities           DirectoryString                   False                    True                    True
c                               DirectoryString                    True                   False                    True
description                     DirectoryString                   False                   False                    True
displayName                     DirectoryString                    True                    True                    True
distinguishedName                            DN                    True                   False                    True
dSCorePropagationData           GeneralizedTime                   False                   False                    True
flags                                       Int                    True                   False                    True
givenName                       DirectoryString                    True                    True                    True
homePhone                       DirectoryString                    True                   False                    True
ipPhone                         DirectoryString                    True                   False                    True
isDeleted                                  Bool                    True                   False                    True
l                               DirectoryString                    True                    True                    True
legacyExchangeDN               CaseIgnoreString                    True                    True                    True
mail                            DirectoryString                    True                    True                    True
manager                                      DN                    True                   False                    True
mSMQDigests                         OctetString                   False                    True                    True
mSMQDigestsMig                      OctetString                   False                   False                    True
mSMQSignCertificates                OctetString                    True                   False                    True
mSMQSignCertificatesMig             OctetString                    True                   False                    True
name                            DirectoryString                    True                    True                    True
o                               DirectoryString                   False                   False                    True
objectGUID                          OctetString                    True                    True                    True
otherIpPhone                    DirectoryString                   False                   False                    True
ou                              DirectoryString                   False                    True                    True
partialAttributeDele…             OctetString                    True                   False                    True
partialAttributeSet                 OctetString                    True                   False                    True
primaryGroupID                              Int                    True                    True                    True
proxiedObjectName                  DNWithBinary                    True                   False                    True
replPropertyMetaData                OctetString                    True                   False                    True
replUpToDateVector                  OctetString                    True                   False                    True
repsFrom                            ReplicaLink                   False                   False                    True
repsTo                              ReplicaLink                   False                   False                    True
sAMAccountType                              Int                    True                    True                    True
securityIdentifier                          Sid                    True                   False                    True
servicePrincipalName            DirectoryString                   False                    True                    True
sIDHistory                                  Sid                   False                    True                    True
sn                              DirectoryString                    True                    True                    True
st                              DirectoryString                    True                   False                    True
street                          DirectoryString                    True                   False                    True
subRefs                                      DN                   False                   False                    True
telephoneNumber                 DirectoryString                    True                   False                    True
userAccountControl                          Int                    True                    True                    True
userCert                            OctetString                    True                   False                    True
userCertificate                     OctetString                   False                   False                    True
userPrincipalName               DirectoryString                    True                    True                    True
userSMIMECertificate                OctetString                   False                   False                    True
uSNChanged                                Int64                    True                    True                    True
uSNCreated                                Int64                    True                    True                    True
uSNLastObjRem                             Int64                    True                   False                    True
wellKnownObjects                   DNWithBinary                   False                   False                    True
whenChanged                     GeneralizedTime                    True                   False                    True
whenCreated                     GeneralizedTime                    True                   False                    True

# Or export to CSV file for later use :

$SchemaUser.get_OptionalProperties() |? {$_.IsInGlobalCatalog}| select name,Syntax,IsSingleValued,IsIndexed,IsInGlobalC
atalog | export-csv -not
Now that we did see how to use the Actice Directory Schema to look up information about the properties, lets take a look at the UserAcountControl Property and UserParameters and note the type(syntax) and Next to properties in global catalog you might also look at the indexed properties, more about that later in the series with directorySearcher.Now we did see how to get all Properties and did set them the way that we did want (think about displayname,GivenName,SN and so on),An Other trick to get the properies is fill them from the MMC and then look at the names created in PowerShell e.g.:

# OriginalPoSH>$user | fl *

objectClass                : {top, person, organizationalPerson, user}
cn                         : {Test}
sn                         : {}
description                : {}
physicalDeliveryOfficeName : {}
telephoneNumber            : {}
givenName                  : {}
initials                   : {}
distinguishedName          : {CN=Test,OU=MowOu,DC=mow,DC=local}
instanceType               : {4}
whenCreated                : {7/2/2006 2:10:58 PM}
whenChanged                : {7/2/2006 2:16:11 PM}
displayName                : {}
uSNCreated                 : {System.__ComObject}
uSNChanged                 : {System.__ComObject}
department                 : {Foo}
name                       : {Test}
objectGUID                 : {4.0.208.232.208.190.162.64.184.228.97.225.109.37.99.125}
userAccountControl         : {544}
badPwdCount                : {0}
codePage                   : {0}
countryCode                : {0}
badPasswordTime            : {System.__ComObject}
lastLogoff                 : {System.__ComObject}
lastLogon                  : {System.__ComObject}
pwdLastSet                 : {System.__ComObject}
primaryGroupID             : {513}
objectSid                  : {1.5.0.0.0.0.0.5.21.0.0.0.94.172.186.232.167.29.117.70.12.60.36.84.61.12.0.0}
accountExpires             : {System.__ComObject}
logonCount                 : {0}
sAMAccountName             : {test}
sAMAccountType             : {805306368}
userPrincipalName          : {}
objectCategory             : {CN=Person,CN=Schema,CN=Configuration,DC=mow,DC=local}
mail                       : {}
nTSecurityDescriptor       : {System.__ComObject}

# Fill some properties in the Management Console.

PoSH>$user.RefreshCache()
PoSH>$user | fl *

objectClass                : {top, person, organizationalPerson, user}
cn                         : {Test}
sn                         : {Orsouw}
description                : {/\/\o\/\/ Test Account}
physicalDeliveryOfficeName : {PowerShell}
telephoneNumber            : {12345}
givenName                  : {Marc}
initials                   : {van}
distinguishedName          : {CN=Test,OU=MowOu,DC=mow,DC=local}
instanceType               : {4}
whenCreated                : {7/2/2006 2:10:58 PM}
whenChanged                : {7/2/2006 2:20:24 PM}
displayName                : {Marc van Orsouw}
uSNCreated                 : {System.__ComObject}
uSNChanged                 : {System.__ComObject}
department                 : {Foo}
name                       : {Test}
objectGUID                 : {4.0.208.232.208.190.162.64.184.228.97.225.109.37.99.125}
userAccountControl         : {544}
badPwdCount                : {0}
codePage                   : {0}
countryCode                : {0}
badPasswordTime            : {System.__ComObject}
lastLogoff                 : {System.__ComObject}
lastLogon                  : {System.__ComObject}
pwdLastSet                 : {System.__ComObject}
primaryGroupID             : {513}
objectSid                  : {1.5.0.0.0.0.0.5.21.0.0.0.94.172.186.232.167.29.117.70.12.60.36.84.61.12.0.0}
accountExpires             : {System.__ComObject}
logonCount                 : {0}
sAMAccountName             : {test}
sAMAccountType             : {805306368}
userPrincipalName          : 
{test@mow.local}
objectCategory             : {CN=Person,CN=Schema,CN=Configuration,DC=mow,DC=local}
mail                       : 
{test@mow.local}
nTSecurityDescriptor       : {System.__ComObject}
We now go on to setting the password and enable the Account. The Set_Password method that is show in the get-member output will only allow you to set your own password, so we need to use the Invoke Method again to pass it to the Native COM object. You can set the Password Like This :Now we need to enable it, as we did see from Your look in the Schema this is a Integer, we also see this as we look at it :

PoSH>$mow.userAccountControl
546
but this is a BitArray setting a whole list of settings. For more info See :

ADS_USER_FLAG_ENUM on MSDN. You can see From that MSDN entry that it is the 2nd Bit that controls “account disabled” True or False.Lets see how we can change that from PowerShell.#Change userAccountControl BitArray# We can go counting and come up with this

$mow.userAccountControl = 544

# Or let PowerShell do the counting.

$user.userAccountControl = $user.userAccountControl[0] -band (-bnot 2)

# We need the userAccountControl[0] as AD porerties are PropertyValueCollections
# while setting you will not notice that as this gets wrapped.

PoSH>$mow.userAccountControl[0] = $mow.userAccountControl -band (-bnot 2)
Cannot convert “System.DirectoryServices.PropertyValueCollection” to “System.Int32″.
At line:1 char:59

# try a gettype :

$user.userAccountControl.gettype()the reason behind the extra [0] will workuserAccountControl is a PropertyValueCollection, you need to set the item.But in the ADSI COM provider are also some shortcuts available that we can use amongs others the Accountdisabled property that does the setting of the bit for us
We can use this functionality like this :

#Change userAccountControl BitArray By using ADSI COM Object Methods :# Get

PoSH>$user.InvokeGet(‘AccountDisabled’)
True
PoSH>$user.userAccountControl
546

# Set 

PoSH>$mow.invokeSet(‘Accountdisabled’,$false)
PoSH>$mow.CommitChanges()
PoSH>$mow.userAccountControl
544
For a list of those ADSI properties and methods see :IADsUser on MSDN.Also setting Terminal TeminalService settings needs to be done with invoke :

PoSH>$mow.InvokeSet(‘TerminalservicesHomeDrive’,“z:”)
PoSH>$mow.CommitChanges()
PoSH>$mow | fl *
objectClass          : {top, person, organizationalPerson, user}
cn                   : {mow}
distinguishedName    : {CN=mow,OU=MowOu,DC=mow,DC=local}
instanceType         : {4}
whenCreated          : {5/18/2006 7:18:00 PM}
whenChanged          : {6/28/2006 8:37:27 PM}
uSNCreated           : {System.__ComObject}
uSNChanged           : {System.__ComObject}
department           : {foodar}
name                 : {mow}
objectGUID           : {231.189.223.77.11.205.61.64.178.101.27.206.71.175.101.75}
userAccountControl   : {546}
badPwdCount          : {0}
codePage             : {0}
countryCode          : {0}
badPasswordTime      : {System.__ComObject}
lastLogoff           : {System.__ComObject}
lastLogon            : {System.__ComObject}
pwdLastSet           : {System.__ComObject}
primaryGroupID       : {513}
userParameters       : {                                                P?CtxCfgPresent?????CtxCfgFlags1?????Ctx
                       Shadow????*??CtxMinEncryptionLevel??&?CtxWFHomeDir???????????????????”??CtxWFHomeDirDrive???}
objectSid            : {1.5.0.0.0.0.0.5.21.0.0.0.94.172.186.232.167.29.117.70.12.60.36.84.84.4.0.0}
accountExpires       : {System.__ComObject}
logonCount           : {0}
sAMAccountName       : {lol}
sAMAccountType       : {805306368}
objectCategory       : {CN=Person,CN=Schema,CN=Configuration,DC=mow,DC=local}
msNPAllowDialin      : {False}
nTSecurityDescriptor : {System.__ComObject}

PoSH>$mow.InvokeGet(‘TerminalservicesHomeDrive’)
Z:

You can see this is stored in a DirectoryString named userParameters that is not really readable , but again there are special Methods here for setting this in the ADSI COM object you can Use with the invoke function , for a Complete list look here on MSDN : IADsTSUserEx Property MethodsIADsTSUserEx Property Methods

We did see in this post how to fill different User Properties and how to enable the Account . And that most properties you can easy set either on the DirectoryEntry or using the Native Com Object,but there are some properties that are stored in a special object therefor the Shortcut Methods in the Com object are more handy (UserAccountControl) or Needed (TerminalService Configuration). Also in ADAM native invokes are more needed.Also if you Come from VbScript and already know the native Method this could be handy, but for the rest is nice to use the wrapped properties as we not need to worry about arrays that much, and can do some exploring in PowerShell.OK, for the first time we needed to do some documentation Lookups, for the invokes but still not that bad I think. and we have all we need for creating the user now, you might want to set some more properties like DisplayName,GivenName,SN,Description etc also but they should be easy to find and set now, in the next post I will do a roundup in some functions, and take a look at CSV import possibilities and then go on to the DirectorySearcher Object.I know this has grown to a big post and there are some things a bit less intuitive as we would want, but thats also the case with other languages and scripting against Active Directory, this are not standard LDAP things and are about how its stored in AD, but we are a bit spoiled with the discover ability of PowerShell already ;-)I will provide more background links later, but I hope this post did clear some things up that seem a bit difficault at first but that they are not that bad, as I did see more examples and questions coming of people using PowerShell with AD in the Comments, the NG and other blogs (Also more about PowerShell in Common for example Scott Hanselmann did post a couple of cool PowerShell usages for links see : http://del.icio.us/powershell and you will see that the PowerShell fueled fire is burning harder and harder ) .

In next post I will make an example combining the Creating and configuring of a User in a Script that is using CSV files to create users.(and the Example how I did create the Users form the AdventureWorks SQL example DBase I created on my Demo DC.I promised Jeffrey Snover ;-) ). And go on to the the Searching of Objects in AD.

As I have been a bit busy …
( PoSH>ConvertTo-Fahrenheit 32 89.6 PoSH>ConvertTo-Fahrenheit 35 95 in the netherlands ;-))
part 4 is a bit later and it’s not yet about the making of scripts as I decided to give some info about using Typedata with the DirectoryEntry Object first in this post.
As this is handy for commandline usage as we can hide some of the more complex Userproperties with typedata, and unlike the WTF example with the boolean I DO keep this update typedata in my profile ;-)
I only will show the adding of the typedata as the rest is covered in former posts.
PowerShel and Active Directory Part 1
Connect to a Domain (Default Naming Context) :
listing properties (format-* commands)
Using get-Member to get information about the object
returned Objects that look like collections could be wrapperobjects. [DirectoryEntries]
how to use get-member on enumerable objects [DirectoryEntries]
using the methods
listing the Child Objects
Getting a Child Object
Connect directly to a Active Directory Object using a path
PowerShell and Active Directory Part 2
Using ADAM to do some testing,
Create a User,
Create a group of users using a Loop
Create an OU or other AD object the same way.
PowerShell and Active Directory Part 3 (UserProperties)
Setting Other properties on the User.
Using ADSI to set properties using InvokeGet, InvokeSet,Invoke
Connecting to Schema using ActiveDirectorySchema class (not work on ADAM.)
how to make a quick function get-Schema
Using native ADSI methods for setting “Special Settings”)
where to find More information on ADSI (accountcontrol Terminal services )
that the Invoke methods are the same a in VbScript so they could be handy for translating VbScripts also.
We will add some properties that are a bit harder to get to the DirectoryObject with a typedata file. so we can use it like this :
# update the Typedata PoSH>Update-TypeData C:\PowerShell\TypeData\directoryEntry.ps1xml

# reset the userAccountControl

PoSH>$mow.userAccountControl = 512

# show added properties 

PoSH>$mow | fl PasswordLastChanged,AccountDisabled,PasswordNeverExpires,userAccountControl

PasswordLastChanged  : 6/29/2006 8:47:30 PM
AccountDisabled      : False
PasswordNeverExpires : False
userAccountControl   : {512}

# using the SetScriptBlock to set the AccountDisabled bit

PoSH>$mow.AccountDisabled = $true
PoSH>$mow | fl PasswordLastChanged,AccountDisabled,PasswordNeverExpires,userAccountControl

PasswordLastChanged  : 6/29/2006 8:47:30 PM
AccountDisabled      : True
PasswordNeverExpires : False
userAccountControl   : {514}

# set Password Never Expires

PoSH>$mow.PasswordNeverExpires = $true
PoSH>$mow | fl PasswordLastChanged,AccountDisabled,PasswordNeverExpires,userAccountControl

PasswordLastChanged  : 6/29/2006 8:47:30 PM
AccountDisabled      : True
PasswordNeverExpires : True
userAccountControl   : {66050}

# Set User must change Password at next Logon 

PoSH>$mow.pwdLastSet = 0
PoSH>$mow | fl PasswordLastChanged,AccountDisabled,PasswordNeverExpires,userAccountControl

PasswordLastChanged  : 
AccountDisabled      : True
PasswordNeverExpires : True
userAccountControl   : {66050}

# UnSet User must change Password at next Logon

PoSH>$mow.pwdLastSet = -1 note that pwdLastSet we can use directly from the DirectoryEntry object but not read,
as its a largeInteger COM Object (more about that later in the series).

the DirectoryEntry.ps1xml file I made for this looks like this :<?xml version=”1.0” encoding=”utf-8” ?>
<Types>
    <Type>
        <Name>System.DirectoryServices.DirectoryEntry</Name>
        <Members>
            <ScriptProperty>
                <Name>PasswordLastChanged</Name>
                 <GetScriptBlock>
                   $this.InvokeGet(‘PasswordLastChanged’)
                </GetScriptBlock>
            </ScriptProperty>
            <ScriptProperty>
                <Name>AccountDisabled</Name>
                 <GetScriptBlock>
                 [bool]($this.userAccountControl[0] -band 2)
                </GetScriptBlock>
                 <SetScriptBlock>
                   if ($args -eq $true) {
                     $this.userAccountControl[0] = $this.userAccountControl[0] -bor (2)
                   }
                   Else {
                     $this.userAccountControl[0] = $this.userAccountControl[0] -band (-bnot 2)
                   }
                </SetScriptBlock>
            </ScriptProperty>
            <ScriptProperty>
                <Name>PasswordNeverExpires</Name>
                 <GetScriptBlock>
                 [bool]($this.userAccountControl[0] -band 65536)
                </GetScriptBlock>
                 <SetScriptBlock>
                   if ($args -eq $true) {
                     $this.userAccountControl[0] = $this.userAccountControl[0] -bor (65536)
                   }
                   Else {
                     $this.userAccountControl[0] = $this.userAccountControl[0] -band (-bnot 65536)
                   }
                </SetScriptBlock>
            </ScriptProperty>
        </Members>
    </Type>
</Types>

You can see that if you add this update-typedata to your profile, and update it for other advanced properties when you run into them (e.g. the terminalserverproperties we did see in part 3) this will build up to a very handy AD library (till it’s standard in V2) making the commandline use very easy as it is attached to the DirectoryEntryType so it works as this is standard.
so again no need to wait for V2 ;-)
In the next part I will go on with the script examples originaly planned for this post, but I found the SetScriptBlock to cool to wait and it took a bit less time.
only I could not find a way to get the args named Param([bool]$value) did not work.

Ok finaly in this 5th part of the series about ActiveDirectory we leave the commandline and are going to make a start to put the bits learned in former post together to make a script to create users from a CSV file.

But first a last bit about listing and exporting the properties of an DirectoryEntry Object in this post as we are going to do an export of the userproperties of a completely configured user to CSV using export-csv,to create a template for our import function to be.

But because the properties are wrapped, as we have seen in former posts, this is not as easy as exporting most other objects in PowerShell, as I will show you in the following example.

For this example I did create and configure a new user (ken Meyer) in the Users & computers MMC and will use that user in the example to create a template for the CSV file to use for creating Users.

# get the testuser

PoSH>$user = New-Object DirectoryServices.DirectoryEntry(‘LDAP://mowdc001/cn=ken Myer,ou=MowOu,dc=mow,dc=local’)
PoSH>$user

distinguishedName
—————–
{CN=Test,OU=MowOu,DC=mow,DC=local}

# list all properties

PoSH>$user | fl *

# Select the properties we want to export

PoSH>$user | select cn,displayname,description,physicalDeliveryOfficeName,telephoneNumber,givenName,initials,sn,company,
homeDrive,homeDirectory,profilePath,scriptPath,sAMAccountName,mail

cn                         : {Ken Myer}
displayName                : {Ken Myer}
description                : {Template User}
physicalDeliveryOfficeName : {room 01-05}
telephoneNumber            : {12345}
givenName                  : {Ken}
initials                   : {}
sn                         : {Myer}
company                    : {Mow}
homeDrive                  : {H:}
homeDirectory              : {\\mowdc001\profile$\KMeyer}
profilePath                : {\\mowdc001\profile$\KMeyer}
scriptPath                 : {Logon.cmd}
sAMAccountName             : {KMeyer}
mail                       : 
{Kmeyer@mowMail.com}

# export this to a CSV file :

PoSH>$user | select cn,displayname,description,physicalDeliveryOfficeName,telephoneNumber,givenName,initials,sn,company,
homeDrive,homeDirectory,profilePath,scriptPath,sAMAccountName,mail | Export-Csv -NoTypeInformation c:\powershell\users.c
sv

# Import it again :

PoSH>import-csv C:\PowerShell\users.csv

cn                         : System.DirectoryServices.PropertyValueCollection
displayName                : System.DirectoryServices.PropertyValueCollection
description                : System.DirectoryServices.PropertyValueCollection
physicalDeliveryOfficeName : System.DirectoryServices.PropertyValueCollection
telephoneNumber            : System.DirectoryServices.PropertyValueCollection
givenName                  : System.DirectoryServices.PropertyValueCollection
initials                   : System.DirectoryServices.PropertyValueCollection
sn                         : System.DirectoryServices.PropertyValueCollection
company                    : System.DirectoryServices.PropertyValueCollection
homeDrive                  : System.DirectoryServices.PropertyValueCollection
homeDirectory              : System.DirectoryServices.PropertyValueCollection
profilePath                : System.DirectoryServices.PropertyValueCollection
scriptPath                 : System.DirectoryServices.PropertyValueCollection
sAMAccountName             : System.DirectoryServices.PropertyValueCollection
mail                       : System.DirectoryServices.PropertyValueCollection

As you can see this is not wat we expected / wanted, a csv file with the values,
the properties are returned as PropertyValueCollection’s, as we have also seen in former posts (a lot of the objects a wrapped the formatters will hide this by enumerating them, but when we export it we run into this as the object will be passed on the pipeline), for this example making a template CSV this would not matter, but as we will run into this (getting back collections) more, while working with objects from the DirectoryServices namespace, as we will run into this also also when we start searching the active directory later in this series I will get into this and how to solve it a bit more in this post.

A way to workaround this is put the properties in scriptblocks (a very powerfull feature of Select-Object), then they get parsed first.
this looks like this :(I added linebreaks after each property in the select statement to make this more clear but you can past this on the PowerShell console as one line )
# Export it again using scriptblocks in the select statement

PoSH>$user | select {$_.cn},
>>   {$_.description},
>>   {$_.physicalDeliveryOfficeName},
>>   {$_.telephoneNumber},
>>   {$_.givenName},
>>   {$_.initials},
>>   {$_.sn},
>>   {$_.company},
>>   {$_.homeDirectory},
>>   {$_.homeDrive},
>>   {$_.scriptPath},
>>   {$_.sAMAccountName},
>>   {$_.mail} | Export-Csv -NoTypeInformation c:\powershell\users.csv
>>

# and import

PoSH>import-csv C:\PowerShell\users.csv

$_.cn                         : Ken Myer
$_.description                : Template User
$_.physicalDeliveryOfficeName : room 01-05
$_.telephoneNumber            : 12345
$_.givenName                  : Ken
$_.initials                   :
$_.sn                         : Myer
$_.company                    : Mow
$_.homeDirectory              : 
\\mowdc001\profile$\KMeyer
$_.homeDrive                  : H:
$_.scriptPath                 : Logon.cmd
$_.sAMAccountName             : KMeyer
$_.mail                       : 
Kmeyer@mowMail.com

you can see that this solves the PropertyValueCollection problem but is more work and also the columnnames contain the expression in the scriptblock also not very friendly, there is good and bad news, the bad news is that it’s more work and typing and the syntax looks strange, the a the good news is we can rename the columns, its very flexible and I hope to show that there is more logic to the sysntax as seems at first.

it works like this, in addition to using a scriptblock for a property in select-object you can also use a hashTable that contains a name key with the wanted name as value and an expression key with the scriptblock to execute as a property.

You also can find more info blog and PowerShell Userguide (selecting properties)
but as the syntax used might still be a bit abstract I will show another example that might make more clear what happens here, as you can see from the next example on the commandline this is not select-object syntax, we are creating a new hashtable and we pass that in :
# commandline example of the HashTable to pass to the select-object Cmdlet

PoSH>@{ name=‘Description’; Expression={$_.description} }

Name                           Value
—-                           —–
name                           Description
Expression                     $_.description
 
PoSH>@{ name=‘Description’; Expression={$_.description} }

Name                           Value
—-                           —–
name                           Description
Expression                     $_.description

PoSH>@{ name=‘Description’; Expression={$_.description} } | gm

   TypeName: System.Collections.Hashtable

Name               MemberType            Definition
—-               ———-            ———-
Add                Method                System.Void Add(Object key, Object value)
Clear              Method                System.Void Clear()
Clone              Method                System.Object Clone()
Contains           Method                System.Boolean Contains(Object key)
ContainsKey        Method                System.Boolean ContainsKey(Object key)
ContainsValue      Method                System.Boolean ContainsValue(Object value)
……

So what happens is that we are just creating a hashtable on the fly for each property, to pass that to select-object and select-object just looks for a name and expression key for information to build the property.

I hope that showing the HashTable on the commandline, made the syntax a bit more logical by seperating it from the select-object statement, and does make the next command more clear, and the syntax eisier to remember, also as some propertynames in Active directory are not realy descriptive (SN) and different from the names in the MMC, I translated them on the way making use of the situation and on top of that,I will show that by making it a filter we can reuse it and use for more purposes.

I made an export like this :# Using select with hastables to rename the properties 

$user | select @{name=‘Name’;Expression={$_.cn} },
  @{ name=‘Description’; Expression={$_.description} },
  @{ name=‘Room’; Expression={$_.physicalDeliveryOfficeName} },
  @{ name=‘Telephone’; Expression={$_.telephoneNumber} },
  @{ name=‘FirstName’; Expression={$_.givenName} },
  @{ name=‘Initials’; Expression={$_.initials} },
  @{ name=‘LastName’; Expression={$_.sn} },
  @{ name=‘Company’; Expression={$_.company} },
  @{ name=‘HomeDir’; Expression={$_.homeDirectory} },
  @{ name=‘HomeDrive’; Expression={$_.homeDrive} },
  @{ name=‘LogonScript’; Expression={$_.scriptPath} },
  @{ name=‘Accountname’; Expression={$_.sAMAccountName} },
  @{ name=‘Mail’; Expression={$_.mail} } | Export-Csv -NoTypeInformation c:\powershell\users.csv

# And import again

PoSH>import-csv C:\PowerShell\users.csv

Name        : Ken Myer
Description : Template User
Room        : room 01-05
Telephone   : 12345
FirstName   : Ken
Initials    :
LastName    : Myer
Company     : Mow
HomeDir     : 
\\mowdc001\profile$\KMeyer
HomeDrive   : H:
LogonScript : Logon.cmd
Accountname : KMeyer
Mail        : 
Kmeyer@mowMail.com

You can see that as most of the code is boilerplate that it’s not as bad as it looks after we get that funky syntax of select-object property-hashtables, and that it is actualy handy as the AD properties are not the same as we see in the MMC interface.
and as you can see below we can make this a filter to make it reusable
# You can make a filter like this : 

filter Format-ADUser {
  $_ | select @{name=‘Name’;Expression={$_.cn} },
    @{ name=‘displayName’; Expression={$_.displayName} },
    @{ name=‘Description’; Expression={$_.description} },
    @{ name=‘Room’; Expression={$_.physicalDeliveryOfficeName} },
    @{ name=‘Telephone’; Expression={$_.telephoneNumber} },
    @{ name=‘FirstName’; Expression={$_.givenName} },
    @{ name=‘Initials’; Expression={$_.initials} },
    @{ name=‘LastName’; Expression={$_.sn} },
    @{ name=‘Department’; Expression={$_.Department} },
    @{ name=‘Company’; Expression={$_.company} },
    @{ name=‘HomeDir’; Expression={$_.homeDirectory} },
    @{ name=‘HomeDrive’; Expression={$_.homeDrive} },
    @{ name=‘LogonScript’; Expression={$_.scriptPath} },
    @{ name=‘Accountname’; Expression={$_.sAMAccountName} },
    @{ name=‘Mail’; Expression={$_.mail} }
}

# And after that reuse it :

PoSH>$user | Format-ADUser | Export-Csv -not C:\PowerShell\users.csv
PoSH>import-csv C:\PowerShell\users.csv

Name        : Ken Myer
displayName : Ken Myer
Description : Template User
Room        : room 01-05
Telephone   : 12345
FirstName   : Ken
Initials    :
LastName    : Myer
Department  : Dep001
Company     : Mow
HomeDir     : 
\\mowdc001\profile$\KMeyer
HomeDrive   : H:
LogonScript : Logon.cmd
Accountname : KMeyer
Mail        : 
Kmeyer@mowMail.com

Now we can also start to play with the data this in available and change it from powershell to make it ready to import again :

some examples :# select some existing users 

PoSH>($ouUsers |? {$_.Department -match ‘Engineering’}).count
6

PoSH>($ouUsers |? {$_.Department -match ‘Engineering’}) | ft name,displayname,department

Name                                    displayName                             Department
—-                                    ———–                             ———-
AWu0003                                 Roberto  Tamburello                     Engineering
AWu0009                                 Gail A Erickson                         Engineering
AWu0011                                 Jossef H Goldberg                       Engineering
AWu0012                                 Terri Lee Duffy                         Engineering
AWu0267                                 Michael I Sullivan                      Engineering
AWu0270                                 Sharon B Salavaria                      Engineering

# format them using the created filter :

PoSH>($ou.get_Children() |? {$_.Department -eq ‘Engineering’}) | format-adUser | ft

Name        displayName Description Room        Telephone   FirstName   Initials    LastName    Department  Company
—-        ———– ———– —-        ———   ———   ——–    ——–    ———-  ——-
AWu0003     Roberto … {}          {}          {}          Roberto     {}          Tamburello  Engineering {}
AWu0009     Gail A E… {}          {}          {}          Gail        {}          Erickson    Engineering {}
AWu0011     Jossef H… {}          {}          {}          Jossef      {}          Goldberg    Engineering {}
AWu0012     Terri Le… {}          {}          {}          Terri       {}          Duffy       Engineering {}
AWu0267     Michael … {}          {}          {}          Michael     {}          Sullivan    Engineering {}
AWu0270     Sharon B… {}          {}          {}          Sharon      {}          Salavaria   Engineering {}

# export them to CSV file :

PoSH>($ou.get_Children() |? {$_.Department -eq ‘Engineering’}) | format-adUser | export-csv -not C:\PowerShell\NewUsers.csv

# the CSV file in text format looks like this :

PoSH>gc C:\PowerShell\NewUsers.csv

Name,displayName,Description,Room,Telephone,FirstName,Initials,LastName,Department,Company,HomeDir,HomeDrive,LogonScrip
t,Accountname,Mail
AWu0003,“Roberto  Tamburello”,,,,Roberto,,Tamburello,Engineering,,,,,$222000-BOCQ2JHU74K1,
AWu0009,“Gail A Erickson”,,,,Gail,,Erickson,Engineering,,,,,$822000-KK9G22LQPGJV,
AWu0011,“Jossef H Goldberg”,,,,Jossef,,Goldberg,Engineering,,,,,$A22000-RFN1CV2D2U5N,
AWu0012,“Terri Lee Duffy”,,,,Terri,,Duffy,Engineering,,,,,$B22000-L10CLJU1NCSO,
AWu0267,“Michael I Sullivan”,,,,Michael,,Sullivan,Engineering,,,,,$AA2000-R7OSI41L9LHR,
AWu0270,“Sharon B Salavaria”,,,,Sharon,,Salavaria,Engineering,,,,,$DA2000-7UPIT07U0FPV,

# And now we can use it as an object

PoSH>$newUsers = import-csv  C:\PowerShell\NewUsers.csv
PoSH>$newusers[0]

Name        : AWu0003
displayName : Roberto  Tamburello
Description :
Room        :
Telephone   :
FirstName   : Roberto
Initials    :
LastName    : Tamburello
Department  : Engineering
Company     :
HomeDir     :
HomeDrive   :
LogonScript :
Accountname : $222000-BOCQ2JHU74K1
Mail        :

# Also now we can do formatting on the job 

# Change the description to “[dep] – [displayname]“

PoSH>$newusers |% {$_.description = “{0} – {1}” -f $_.Department,$_.displayname}
PoSH>$newusers | ft name,description

Name                                                        Description
—-                                                        ———–
AWu0003                                                     Engineering – Roberto  Tamburello
AWu0009                                                     Engineering – Gail A Erickson
AWu0011                                                     Engineering – Jossef H Goldberg
AWu0012                                                     Engineering – Terri Lee Duffy
AWu0267                                                     Engineering – Michael I Sullivan
AWu0270                                                     Engineering – Sharon B Salavaria

# note that FT standard does not show the empty properties

PoSH>$newusers | ft

Name        displayName Description Room        Telephone   FirstName   Initials    LastName    Department  Company
—-        ———– ———– —-        ———   ———   ——–    ——–    ———-  ——-
AWu0003     Roberto … Engineer…                         Roberto                 Tamburello  Engineering
AWu0009     Gail A E… Engineer…                         Gail                    Erickson    Engineering
AWu0011     Jossef H… Engineer…                         Jossef                  Goldberg    Engineering
AWu0012     Terri Le… Engineer…                         Terri                   Duffy       Engineering
AWu0267     Michael … Engineer…                         Michael                 Sullivan    Engineering
AWu0270     Sharon B… Engineer…                         Sharon                  Salavaria   Engineering

# show all properties :

PoSH>$newusers | ft *

Name    display Descrip Room    Telepho FirstNa Initial LastNam Departm Company HomeDir HomeDri LogonSc Account Mail
        Name    tion            ne      me      s       e       ent                     ve      ript    name
—-    ——- ——- —-    ——- ——- ——- ——- ——- ——- ——- ——- ——- ——- —-
AWu0003 Robe… Engi…                 Roberto         Tamb… Engi…                                 $222
AWu0009 Gail… Engi…                 Gail            Eric… Engi…                                 $822
AWu0011 Joss… Engi…                 Jossef          Gold… Engi…                                 $A22…
AWu0012 Terr… Engi…                 Terri           Duffy   Engi…                                 $B22…
AWu0267 Mich… Engi…                 Michael         Sull… Engi…                                 $AA2…
AWu0270 Shar… Engi…                 Sharon          Sala… Engi…                                 $DA2…

# change the name value 

PoSH>$newusers |% {$_.name = $_.name.replace(‘AWu’,‘NewUser’)}
PoSH>$newusers | ft name,description

Name                                                        Description
—-                                                        ———–
NewUser0003                                                 Engineering – Roberto  Tamburello
NewUser0009                                                 Engineering – Gail A Erickson
NewUser0011                                                 Engineering – Jossef H Goldberg
NewUser0012                                                 Engineering – Terri Lee Duffy
NewUser0267                                                 Engineering – Michael I Sullivan
NewUser0270                                                 Engineering – Sharon B Salavaria

As you can see I did change the Description and the Name of the users,
if we now do a new export-csv of the data we are able to make bulkchanges to the records to import and we can do the rest of the editing in excel.

you can see that working with CSV files like this is very powerfull in POwerShell, as you also could see in my CSV series before,
in the next part we will use the CSV file generated and edited to finaly start importing the Users from the file into active directory.

In this part sixth part of the AD series we will make a start of the script to import / create users from a CSV file (template generated in part 5 of this series).# Load the CSV file with users 

$newUsers = import-csv  C:\PowerShell\NewUsers.csv 

# the first user looks like this (see part 5)

PoSH>$newusers[0]

Name        : NewUser0003
displayName : Roberto  Tamburello
Description : Engineering – Roberto  Tamburello
Room        :
Telephone   :
FirstName   : Roberto
Initials    :
LastName    : Tamburello
Department  : Engineering
Company     :
HomeDir     :
HomeDrive   :
LogonScript :
Accountname : $222000-BOCQ2JHU74K1
Mail        : 

I will start by just echo-ing some of the properties to the screen in the first examples.
and I will start using a filter as we provide the objects on the pipeline :
# most Simple form Filter

filter new-AdUser {
  if ($_.Name) {“CN=$($_.name)”Else {throw “Name is Mandatory”}
}

# Check on the Name Property

PoSH>“foo” | new-AdUser
Name is Mandatory
At line:2 char:45
+   if ($_.Name) {“CN=$($_.name)”Else {throw  <<<< “Name is Mandatory”}

# if objects in pipeline have name property

PoSH>$newusers | new-AdUser
CN=NewUser0003
CN=NewUser0009
CN=NewUser0011
CN=NewUser0012
CN=NewUser0267
CN=NewUser0270

we will later switch to a function for this but I will start with a filter to show the difference

in this first filter you can see that $_ contains the current item and we can access the different field from the CSV file from the object, in this case we just echo it to the screen.

as we have seen in the second part of this series, all we really need to create a User is a CN, but we have also seen that the SamAccountname another mandatory property gets generated in that case and that that is not realy handy, as we most of the time want this the same as the CN name lets add a check for the property and if it’s not there lets add it.# added SamAccountname

filter new-AdUser {
  # check for name field :

  if ($_.Name) {“CN=$($_.name)”Else {throw “Name is Mandatory”}

  # If accountname is not given make it same as CN

  if ($_.Accountname) {“SamAccountName = $($_.Accountname)”Else {“SamAccountName = $($_.name)”}
}

# objects in pipeline have accountname property

PoSH>$newusers | new-AdUser

CN=NewUser0003
SamAccountName = $222000-BOCQ2JHU74K1
CN=NewUser0009
SamAccountName = $822000-KK9G22LQPGJV
CN=NewUser0011
SamAccountName = $A22000-RFN1CV2D2U5N
CN=NewUser0012
SamAccountName = $B22000-L10CLJU1NCSO
CN=NewUser0267
SamAccountName = $AA2000-R7OSI41L9LHR
CN=NewUser0270
SamAccountName = $DA2000-7UPIT07U0FPV 

# Remove accountname property 

PoSH>$newUsers |% {$_.accountname = “”}

# Now the SamAccountname will be generated the same as the CN

PoSH>$newusers | new-AdUser

CN=NewUser0003
SamAccountName = NewUser0003
CN=NewUser0009
SamAccountName = NewUser0009
CN=NewUser0011
SamAccountName = NewUser0011
CN=NewUser0012
SamAccountName = NewUser0012
CN=NewUser0267
SamAccountName = NewUser0267
CN=NewUser0270
SamAccountName = NewUser0270

As you can see, you can use this to keep the CSV file simple but extendable (you don’t need to provide the samaccountname if you want it to be the same as the CN, but you CAN provide it if this is not the case ).
also as my exported users had generated samaccountname properties, I did clear them all so the samacountnames will be the same as the CN.

now to create the users we also need the the OU where to import the user, I will add this as a parameter to the function, but also make a check for a OU field in the CSV file to overrule this on a per user base.
also I will show some examples of how the OU gets parsed so you can use an OU object or the LDAP string :
# OU to import parameter

PoSH>[System.DirectoryServices.DirectoryEntry]$ImportOu = $mowOu
PoSH>[System.DirectoryServices.DirectoryEntry]$ImportOu = “OU=MowOu,DC=mow,DC=local”
PoSH>$ImportOu
out-lineoutput : Exception retrieving member “ClassId2e4f51ef21dd47e99d3c952918aff9cd”: “Unspecified error

# LDAP path to import parameter

PoSH>[System.DirectoryServices.DirectoryEntry]$ImportOu = “LDAP://OU=MowOu,DC=mow,DC=local”
PoSH>$ImportOu

distinguishedName
—————–
{OU=MowOu,DC=mow,DC=local}

#  list possible constuctors

PoSH>[System.DirectoryServices.DirectoryEntry].GetConstructors() |% {“$_”}
Void .ctor()
Void .ctor(System.String)
Void .ctor(System.String, System.String, System.String)
Void .ctor(System.String, System.String, System.String, System.DirectoryServices.AuthenticationTypes)
Void .ctor(System.Object)
PoSH>[System.DirectoryServices.DirectoryEntry]$ImportOu = “LDAP://OU=MowOu,DC=mow,DC=local”
PoSH>$ImportOu

distinguishedName
—————–
{OU=MowOu,DC=mow,DC=local}

# Take care the LDAP path is CaseSensitive 

PoSH>[System.DirectoryServices.DirectoryEntry]$ImportOu = “LDaP://OU=MowOu,DC=mow,DC=local”
PoSH>$ImportOu
out-lineoutput : Exception retrieving member “ClassId2e4f51ef21dd47e99d3c952918aff9cd”“Unknown error (0x80005000)”
PoSH>

# added OU as parameter and as field to the filter

filter new-AdUser ([System.DirectoryServices.DirectoryEntry]$importOU){
  # check for name field :

  if ($_.Name) {“CN=$($_.name)”Else {throw “Name is Mandatory”}

  # If accountname is not given make it same as CN

  if ($_.Accountname) {“SamAccountName = $($_.Accountname)”Else {“SamAccountName = $($_.name)”}

  if ($_.OU) {“OU = $($_.OU)”Else {“OU = $($ImportOU.distinguishedName)”}

}

# output now looks like this :

PoSH>$newusers | new-AdUser $mowou
CN=NewUser0003
SamAccountName = NewUser0003
OU=MowOu,DC=mow,DC=local
CN=NewUser0009
SamAccountName = NewUser0009
OU=MowOu,DC=mow,DC=local
CN=NewUser0011
SamAccountName = NewUser0011
OU=MowOu,DC=mow,DC=local
CN=NewUser0012
SamAccountName = NewUser0012
OU=MowOu,DC=mow,DC=local
CN=NewUser0267
SamAccountName = NewUser0267
OU=MowOu,DC=mow,DC=local
CN=NewUser0270
SamAccountName = NewUser0270
OU=MowOu,DC=mow,DC=local

# Some extra tricks using select like in part 5 of this series

# using select to add the OU property on the fly

PoSH>$newusers | select *,@{e={“OU=MowOtherOU,DC=mow,DC=local”};name=‘OU’} | new-adUser
CN=NewUser0003
SamAccountName = NewUser0003
OU = OU=MowOtherOU,DC=mow,DC=local
CN=NewUser0009
SamAccountName = NewUser0009
OU = OU=MowOtherOU,DC=mow,DC=local
CN=NewUser0011
SamAccountName = NewUser0011
OU = OU=MowOtherOU,DC=mow,DC=local
CN=NewUser0012
SamAccountName = NewUser0012
OU = OU=MowOtherOU,DC=mow,DC=local
CN=NewUser0267
SamAccountName = NewUser0267
OU = OU=MowOtherOU,DC=mow,DC=local
CN=NewUser0270
SamAccountName = NewUser0270
OU = OU=MowOtherOU,DC=mow,DC=local

# or combine the standard OU with some overruled users

PoSH>$newusers | select *,@{e={if ($_.name -eq ‘NewUser0270′){“OU=MowOtherOU,DC=mow,DC=local”else {“”}};name=‘OU’} | new-adUser $mowOU

CN=NewUser0003
SamAccountName = NewUser0003
OU = OU=MowOu,DC=mow,DC=local
CN=NewUser0009
SamAccountName = NewUser0009
OU = OU=MowOu,DC=mow,DC=local
CN=NewUser0011
SamAccountName = NewUser0011
OU = OU=MowOu,DC=mow,DC=local
CN=NewUser0012
SamAccountName = NewUser0012
OU = OU=MowOu,DC=mow,DC=local
CN=NewUser0267
SamAccountName = NewUser0267
OU = OU=MowOu,DC=mow,DC=local
CN=NewUser0270
SamAccountName = NewUser0270
OU = OU=MowOtherOU,DC=mow,DC=local

# update the CSV file 

PoSH>$newusers | select *,@{e={if ($_.name -eq ‘NewUser0270′){“OU=MowOtherOU,DC=mow,DC=local”else {“”}};name=‘OU’} | e
xport-csv -not c:\powershell\modUsers.csv
PoSH>import-csv c:\powershell\modUsers.csv | ft name,ou

Name                                                        OU
—-                                                        –
NewUser0003
NewUser0009
NewUser0011
NewUser0012
NewUser0267
NewUser0270                                                 OU=MowOtherOU,DC=mow,DC=local

Note that you can just edit the CSV file but I did use select again like in the export examples in part 5 of the AD series,to show that you don’t need to use the object and can nest any kind of script in in.

Now we have all the properties to start lets change the script to not only echo the properties but actualy do something.

## the first version that is actualy working :
 
filter new-AdUser ([System.DirectoryServices.DirectoryEntry]$importOU){

  # check for name field :

  if ($_.Name) {$CN= $_.name} Else {throw “Name is Mandatory”}

  # Use default OU or connect to OU given in CSV file 

  if ($_.OU) {
      $OU = new-object System.DirectoryServices.DirectoryEntry(“LDAP://$($_.OU)”)
  } 
  Else {
      $OU = $ImportOU
  }

  # create the new user :

  $NewUser = $ou.get_Children().add(“CN=$($_.name)”,‘user’)
  $NewUser.commitChanges()

  # If accountname is not given make it same as CN

  if ($_.Accountname) {$newUser.sAMAccountName = $_.Accountname} Else {$newUser.sAMAccountName = $_.name}
  $NewUser.commitChanges()

}

Now let’s create the users in the CSV file, where I will overrule the OU for one account.

# make other OU to test

PoSH>($root.get_Children().Add(“OU=MowOtherOU”,“organizationalUnit”)).commitChanges()

# one account on other OU 

PoSH>import-csv c:\powershell\modUsers.csv | new-AdUser  $mowou

distinguishedName
—————–
{OU=MowOu,DC=mow,DC=local}
SamAccountName = NewUser0003
{OU=MowOu,DC=mow,DC=local}
SamAccountName = NewUser0009
{OU=MowOu,DC=mow,DC=local}
SamAccountName = NewUser0011
{OU=MowOu,DC=mow,DC=local}
SamAccountName = NewUser0012
{OU=MowOu,DC=mow,DC=local}
SamAccountName = NewUser0267
{OU=MowOtherOU,DC=mow,DC=local}
SamAccountName = NewUser0270

# check if the users are there :

PoSH>$mowou.get_Children() |? {$_.name -like “NewUser*”} | ft cn,distinguishedName,sAMAccountName

cn                                      distinguishedName                       sAMAccountName
—                                      —————–                       ————–
{NewUser0003}                           {CN=NewUser0003,OU=MowOu,DC=mow,DC=l… {NewUser0003}
{NewUser0009}                           {CN=NewUser0009,OU=MowOu,DC=mow,DC=l… {NewUser0009}
{NewUser0011}                           {CN=NewUser0011,OU=MowOu,DC=mow,DC=l… {NewUser0011}
{NewUser0012}                           {CN=NewUser0012,OU=MowOu,DC=mow,DC=l… {NewUser0012}
{NewUser0267}                           {CN=NewUser0267,OU=MowOu,DC=mow,DC=l… {NewUser0267}
{NewUser0270}                           {CN=NewUser0270,OU=MowOu,DC=mow,DC=l… {NewUser0270}

# Clean up created users for next test

PoSH>$mowou.get_Children() |? {$_.name -like “NewUser*”} |% {$mowou.get_Children().Remove($_)}
PoSH>$mowou.get_Children() |? {$_.name -like “NewUser*”} | ft cn,distinguishedName,sAMAccountName
PoSH>

Thats it for now, in the next post we will add some more properties and enable them etc. as in the third part of this series.also we will add some more checking and errorhandling etc, make it into a function on the way, and will add some more functionality as creating the Homedirectory etc. as the MMC also does.

In this seventh part of this AD series, we will expand the filter a little,but first we need to provide the properties,so we need to fill the CSV file a bit more with the fields needed.
You can just start-up excel to edit it but here I will use some more powershell onliners to fill them from the results of the export I did of some of the users I created from the adventureworks sample DB for my demosetup and did export in in
PowerShell and Active Directory Part 5 , I did place get content to show what the current content is and will go on from there, using the onliners on the commandline to fill them, but you can also use the Ken Meyer user we created also in that post with the MMC and exported that onle is allready complete(for now). for more about CSV handling see more Monad scripts, and a bit more CSV and the links there for my CSV series.
# use invoke-item (ii) to invoke Excel to edit 

PoSH>ii modUsers.csv

# the file with six test users i have now

PoSH>cat modUsers.csv
Name,displayName,Description,Room,Telephone,FirstName,Initials,LastName,Department,Company,HomeDir,HomeDrive,LogonScript,Accountname,Mai

l,OU
NewUser0003,“Roberto  Tamburello”,“Engineering – Roberto  Tamburello”,,,Roberto,,Tamburello,Engineering,,,,,,,
NewUser0009,“Gail A Erickson”,“Engineering – Gail A Erickson”,,,Gail,,Erickson,Engineering,,,,,,,
NewUser0011,“Jossef H Goldberg”,“Engineering – Jossef H Goldberg”,,,Jossef,,Goldberg,Engineering,,,,,,,
NewUser0012,“Terri Lee Duffy”,“Engineering – Terri Lee Duffy”,,,Terri,,Duffy,Engineering,,,,,,,
NewUser0267,“Michael I Sullivan”,“Engineering – Michael I Sullivan”,,,Michael,,Sullivan,Engineering,,,,,,,
NewUser0270,“Sharon B Salavaria”,”Engineering – Sharon B 

Salavaria“,,,Sharon,,Salavaria,Engineering,,,,,,,”OU=MowOtherOU,DC=mow,DC=local”

# set some properties

PoSH>$newusers |% {$_.HomeDir = “H:”}
PoSH>$newusers |% {$_.Company = “PoSH works”}
PoSH>$newusers |% {$_.LogonScript = “Logon.cmd”}
PoSH>$newusers |% {$_.HomeDrive = “H:”}
PoSH>$newusers |% {$_.HomeDir = \\mowdc001\home$\$($_.name)}

# show first user

PoSH>$newusers[0]

Name        : NewUser0003
displayName : Roberto  Tamburello
Description : Engineering – Roberto  Tamburello
Room        :
Telephone   :
FirstName   : Roberto
Initials    :
LastName    : Tamburello
Department  : Engineering
Company     : PoSH works
HomeDir     : H:
HomeDrive   : 
\\mowdc001\home$\NewUser0003
LogonScript : Logon.cmd
Accountname :
Mail        :
OU          :

# fill some rooms

PoSH>1..3 |% {$newUsers[$_].room = “room $_”}

# try some formating on the mailaddress

PoSH>$newUsers |% {{0}.{1}@Mowmail.com -f $_.Firstname,$_.LastName}
Roberto.Tamburello@Mowmail.com
Gail.Erickson@Mowmail.com
Jossef.Goldberg@Mowmail.com
Terri.Duffy@Mowmail.com
Michael.Sullivan@Mowmail.com
Sharon.Salavaria@Mowmail.com

PoSH>$newUsers |% {{0}.{1}@Mowmail.com -f $_.Firstname.Substring(0,1),$_.LastName}
R.Tamburello@Mowmail.com
G.Erickson@Mowmail.com
J.Goldberg@Mowmail.com
T.Duffy@Mowmail.com
M.Sullivan@Mowmail.com
S.Salavaria@Mowmail.com

# fill that also

PoSH>$newUsers |% {$_.mail = ({0}.{1}@Mowmail.com -f $_.Firstname.Substring(0,1),$_.LastName)}

PoSH>$newUsers | ft name,room,mail -a

Name        Room   Mail
—-        —-   —-
NewUser0003        
R.Tamburello@Mowmail.com
NewUser0009 room 1 
G.Erickson@Mowmail.com
NewUser0011 room 2 
J.Goldberg@Mowmail.com
NewUser0012 room 3 
T.Duffy@Mowmail.com
NewUser0267        
M.Sullivan@Mowmail.com
NewUser0270        
S.Salavaria@Mowmail.com

Ok enough CSV handling and looping tricks, lets get back AD that we are here fore and to the script to create.

In the following script I addded the following :

- Displayname
I did work out 3 possiblities for that,
1 it was given in the CSV file, so we just fill it
2 It was not given but we have a firstname and lastname so we generate it (as the MMC does)
3 displayname first and last name or not given set it to the name
– Some other otional properties
– if homeDirectory is given the Creating of homedirectories (as the MMC does)
– setting the initial password
– useraccount control to 512 (default) to setenabled (see former posts)

the filter now looks like this :filter new-AdUser ([System.DirectoryServices.DirectoryEntry]$importOU){ 

  # check for name field : 

  if ($_.Name) {$CN= $_.name} Else {throw “Name is Mandatory”

  # Use default OU or connect to OU given in CSV file  

  if ($_.OU) { 
      $OU = new-object System.DirectoryServices.DirectoryEntry(“LDAP://$($_.OU)”
  }  
  Else { 
      $OU = $ImportOU 
  } 

  # create the new user : 
  Write-host -fore “Yellow” “Creating $($_.name)”

  $NewUser = $ou.get_Children().add(“CN=$($_.name)”,‘user’
  $NewUser.commitChanges() 

  # If accountname is not given make it same as CN 

  if ($_.Accountname) {$newUser.sAMAccountName = $_.Accountname} Else {$newUser.sAMAccountName = $_.name} 
  $NewUser.commitChanges() 

  # Process DisplayName

  if ($_.DisplayName) {
    # displayname given so use that
    $newUser.DisplayName = $_.DisplayName
  }
  ElseIf ($_.firstname -and $_.lastname) {
    # no displayname but lastname and firstname given generate Displayname
    $newUser.DisplayName = “$($_.LastName), $($_.FirstName) $($_.Initials)”
  }
  Else {
    # just use the UserName
    $newUser.DisplayName = $_.Name
  }

  # Fill Other Properties if given :

  if ($_.Firstname) {$newUser.GivenName = $_.Firstname}
  if ($_.Lastname) {$newUser.SN = $_.LastName}
  if ($_.Initials) {$newUser.Initials = $_.Initials}

  if ($_.Description) {$newUser.Description = $_.Description}
  if ($_.Room) {$newUser.physicalDeliveryOfficeName = $_.Room}
  if ($_.Telephone) {$newUser.telephoneNumber = $_.Telephone}
  if ($_.Department) {$newUser.Department = $_.Department}
  if ($_.Mail) {$newUser.Mail = $_.Mail}
  if ($_.HomeDrive) {$newUser.HomeDrive = $_.HomeDrive}
 
  # Fill HomeDir and try to create the folder

  if ($_.HomeDir) {
    $newUser.homeDirectory = $_.HomeDir

    # check if homeDir does exist, and if its possible to create

    if (Test-Path $_.HomeDir) {
      “HomeDir Does Exist”
    }
    Else {
      if (split-Path $_.HomeDir -Parent) {
        “Parent Found, Share $($_.HomeDir) can be created”
        $ud = md $_.HomeDir
        $ar = new-object Security.AccessControl.FileSystemAccessRule($_.name,‘Modify, Synchronize’,‘ContainerInherit, ObjectInherit’,‘none’,‘Allow’)
        $ac = $ud.GetAccessControl()
        $ac.AddAccessRule($ar)
        Set-Acl $ud $ac
      }
      Else{
        “$(split-Path $_.HomeDir -Parent) not Found, Create share Manual”
      }
    }
  }

  if ($_.LogonScript) {$newUser.scriptPath = $_.LogonScript}

  # set initial password and force user to change it at first logon

  $newUser.Invoke(“SetPassword”,“P@ssW0Rd”)
  $newUser.pwdLastSet = 0

  # Enable User
  $newUser.userAccountControl = 512

}

note that as this is a filter not a script or function yet so all the code gets executed for every record,
we will change that in next post as for the other things I want to do I want to add some code that will only run once,
and we will add some more properties and errorhandling you will see the differance and use of this in next post also and work on the output.

the output looks like this :PoSH>$mowou.get_Children() |? {$_.name -like “NewUser*”} |% {$mowou.get_Children().Remove($_)}
PoSH>$newusers | new-aduser $mowou

Creating NewUser0003
Parent Found, Share 
\\mowdc001\home$\NewUser0003 can be created
Creating NewUser0009
Parent Found, Share 
\\mowdc001\home$\NewUser0009 can be created
Creating NewUser0011
Parent Found, Share 
\\mowdc001\home$\NewUser0011 can be created
Creating NewUser0012
Parent Found, Share 
\\mowdc001\home$\NewUser0012 can be created
Creating NewUser0267
Parent Found, Share 
\\mowdc001\home$\NewUser0267 can be created
Creating NewUser0270
Parent Found, Share 
\\mowdc001\home$\NewUser0270 can be created

# only the basic errorhandling (270 user allready exists)

PoSH>$mowou.get_Children() |? {$_.name -like “NewUser*”} |% {$mowou.get_Children().Remove($_)}
PoSH>$newusers | new-aduser $mowou

Creating NewUser0003
HomeDir Does Exist
Creating NewUser0009
HomeDir Does Exist
Creating NewUser0011
HomeDir Does Exist
Creating NewUser0012
HomeDir Does Exist
Creating NewUser0267
HomeDir Does Exist
Creating NewUser0270
Exception calling “CommitChanges” with “0” argument(s): “The object already exists. (Exception from HRESULT: 0x80071392
)”
At line:14 char:25
+   $NewUser.commitChanges( <<<< )
Exception setting “sAMAccountName”“A constraint violation occurred. (Exception from HRESULT: 0x8007202F)”
At line:16 char:81
+   if ($_.Accountname) {$newUser.sAMAccountName = $_.Accountname} Else {$newUser.s <<<< AMAccountName = $_.name}
Exception calling “CommitChanges” with “0” argument(s): “A constraint violation occurred. (Exception from HRESULT: 0x80
07202F)”
At line:17 char:25
+   $NewUser.commitChanges( <<<< )
Exception setting “DisplayName”“A constraint violation occurred. (Exception from HRESULT: 0x8007202F)”
At line:21 char:14
+     $newUser.D <<<< isplayName = $_.DisplayName
Exception setting “GivenName”“A constraint violation occurred. (Exception from HRESULT: 0x8007202F)”
At line:32 char:31
+   if ($_.Firstname) {$newUser.G <<<< ivenName = $_.Firstname}
Exception setting “SN”“A constraint violation occurred. (Exception from HRESULT: 0x8007202F)”
At line:33 char:30
+   if ($_.Lastname) {$newUser.S <<<< N = $_.LastName}

# and the result 

PoSH>$mowou.get_Children() |? {$_.cn -match ‘NewUser’} | format-aduser | ft

Name        displayName Description Room        Telephone   FirstName   Initials    LastName    Department  Company
—-        ———– ———– —-        ———   ———   ——–    ——–    ———-  ——-
NewUser0003 Roberto … Engineer… {}          {}          Roberto     {}          Tamburello  Engineering {}
NewUser0009 Gail A E… Engineer… room 1      {}          Gail        {}          Erickson    Engineering {}
NewUser0011 Jossef H… Engineer… room 2      {}          Jossef      {}          Goldberg    Engineering {}
NewUser0012 Terri Le… Engineer… room 3      {}          Terri       {}          Duffy       Engineering {}
NewUser0267 Michael … Engineer… {}          {}          Michael     {}          Sullivan    Engineering {}

PoSH>($mowou.get_Children() |? {$_.cn -match ‘NewUser’} | format-aduser)[0]

Name        : NewUser0003
displayName : Roberto  Tamburello
Description : Engineering – Roberto  Tamburello
Room        : {}
Telephone   : {}
FirstName   : Roberto
Initials    : {}
LastName    : Tamburello
Department  : Engineering
Company     : {}
HomeDir     : 
\\mowdc001\home$\NewUser0003
HomeDrive   : H:
LogonScript : Logon.cmd
Accountname : NewUser0003
Mail        : 
R.Tamburello@Mowmail.com

Note that I made one user in another OU and did not delete that user, every line will give his own error and everything is still triedand will fail we work on that also in next post, as for large numbers this is not handy but for a couple of users this doable.
I think all the AD stuff was covert in former posts, so I won’t explain that again here and the rest of the filter is straight forward I think, except for setting the security on the directory I covert this before in this post :
Adding a Simple AccesRule to a file ACL in MSH and see the links in there for more info, so this is it for now.

This eighth part of the AD series is the *Edit* change of plans, see rest of blogentry, there will be one more, (one before) last about creating users in Active Directory,
in this post we will transform the filter used in last post to a more robust function, and transform it in a more production quality script.

*Note* Disclaimer, while I discus the posibilities for making a production quality script the example script I provide in this post is only a example on how to start, not complete and tested script, so only use this in a test environment also be sure to have a basic understanding
of how the script works, better yet read the other posts in this series first!

Part 1 to 3 of this series handle the making of a user from the start from the commandline, an overview what you can find where you can find in the beginning of part 4 ,
Part 5 – 7 handle the way to use CSV files or any other way to fill the PSObject with the needed properties, and this function.

PowerShell and Active Directory Part 7
PowerShell and Active Directory Part 6
PowerShell and Active Directory Part 5
PowerShell and Active Directory Part 4 (TypeData)
PowerShell and Active Directory Part 3 (UserProperties)
PowerShell and Active Directory Part 2
PowerShel and Active Directory Part 1

First about the change to a script, the Filter I used is easy to test with as you can just past it to the commandline, this version is a script that you can run directly.

for another example see the changes Mike Hodnick made to an out-zip function I made before MSH out-zip Function in his blogpost here Create zip/compressed folders with Powershell to make it into a scriptas it was “but it wasn’t exactly 100% usable out of the box”.

The only thing that needed changing is the extension (MSH to PS1) and it works,
only this script was made to create / load the function :

. c:\powershell\fuctions\out-zip.ps1

note the [dot][space] before the scriptpath, that makes that the function is loaded into the $global scope, otherwise it is gone again after the script ends.
It is gone also if you close the PowerShell console but I load this in my profile to keep it handy.
As I mostly test by pasting code into the console most of my examples are like this, so you can just past them in from my blog to test.

So it both handy out-of-the box I think but in an other way and / or a different purpose.
it’s good to compare the 2 scripts to see wat I mean.
In this case I also did choose to switch to a scriptfile to run and let it call internal functions.
for some more info and another example see also :
PowerShell AD site Finder this script has the same form, also the switch parameter and byRef I discuss there.

For the setting “User can not change password” I did not yet discuss that in Part 3 about setting the special properties, this is actualy a ACL on the directoryentry and you can not set it using ADSI, only with the NT provider.
You can not use the WinNT provider with a DirectoryEntry Object, but there are some workarounds see also :
Access ADSI WinNT provider from Monad Part 2

You see there are 2 workarounds,
The First I found was inline VB.NET code
MSH access NT provider
The second was the Reflection example in part 2,that way, Importing the VisualBasic namespace , I show here :

# Setting User can not change password with NT provider using the VisualBasic namespace

[System.Reflection.Assembly]::LoadWithPartialName(‘microsoft.visualbasic’)

$winNT = [microsoft.visualbasic.interaction]::GetObject(“WinNT://mow/mow”,$null)
$Type = $winNT.gettype()
$Type.invokemember(“userFlags”,‘GetProperty’,$null,$winNT,$null) -band 0x40

$new = $Type.invokemember(“userFlags”,‘GetProperty’,$null,$winNT,$null) -bor 0x40
$Type.invokemember(“userFlags”,‘SetProperty’,$null,$winNT,$new)
# Save the changes

$Type.invokemember(“setinfo”,‘invokemethod’,$null,$winNT,$null)

But as it is actualy an ACL on the directory object we can also change the security on the User object ourself, see for more information here : MDSN User Cannot Change Password (LDAP Provider) (Be sure to follow the links to the different examples )
for a VbScript see example :
HilltopLabs : cannot Change Password

This looks very difficult and big scripts are needed, but we have powershell ;-)
So this looks like this in the script :
  # Set User Cannot Change Password Property

  if ($_.CantChangePassword) {

    $everyOne = [System.Security.Principal.SecurityIdentifier]‘S-1-1-0′
    $EveryoneDeny = new-object System.DirectoryServices.ActiveDirectoryAccessRule ($Everyone,‘ExtendedRight’,‘Deny’,‘ab721a53-1e2f-11d0-9819-00aa0040529b’)
    $self = [System.Security.Principal.SecurityIdentifier]‘S-1-5-10′
    $SelfDeny = new-object System.DirectoryServices.ActiveDirectoryAccessRule ($self,‘ExtendedRight’,‘Deny’,‘ab721a53-1e2f-11d0-9819-00aa0040529b’)

    $newUser.get_ObjectSecurity().AddAccessRule($selfDeny)
    $newUser.get_ObjectSecurity().AddAccessRule($EveryoneDeny)

    $newUser.CommitChanges()

  }

Not to Bad right, but note that the other examples do some more checking and can set it back also, as we create new users here we do not need all this.

*Edit* I decided this subject was important enough to get his own blogentry and found back some old material from when I did not have internet I did not post yet, so I will post that material here and will continue the script in next post.

In the samples below you can also see how to explore the AD security from the commandline and also to check the current ACL’s and remove the Access denied rules.
you can see that it is also very easy, also I will discuss the User must change password at next logon property in this examples, setting pwdLastset to -1 or 0
will switch that (it can not be set in combination the the User can not change password)

# Remove Access denied from commandline

$mow.get_ObjectSecurity().GetAccessRules($true,$false, [System.Security.Principal.SecurityIdentifier] )

$mow.get_ObjectSecurity().GetAccessRules($true,$false, [System.Security.Principal.NTAccount]) |? {$_.ObjectType -eq ‘ab721a53-1e2f-11d0-9819-00aa0040529b’}
$mr = $mow.get_ObjectSecurity().GetAccessRules($true,$false, [System.Security.Principal.NTAccount]) |? {$_.ObjectType -eq ‘ab721a53-1e2f-11d0-9819-00aa0040529b’} |? {$_.AccessControlType -eq ‘Deny’}

$mow.get_ObjectSecurity().RemoveAccessRule($mr[0])

$mow.get_ObjectSecurity().RemoveAccessRule($mr[1])

$mow.CommitChanges()

########################################

PoSH>$mr[0].IdentityReference | gm

   TypeName: System.Security.Principal.NTAccount

[System.Security.Principal.SecurityIdentifier].GetConstructors() |% {“$_”}

##############

# Make SID Objects 

$everyOne = [System.Security.Principal.SecurityIdentifier]‘S-1-1-0′
$self = [System.Security.Principal.SecurityIdentifier]‘S-1-5-10′

# you can use NTAccount but thats localized 

# translate 

PoSH>$self.Translate( [System.Security.Principal.NTAccount])

Value
—–
NT AUTHORITY\SELF

# get the consructors

[System.DirectoryServices.ActiveDirectoryAccessRule].GetConstructors() |% {“$_”}
 

# we are going to use this one :

Void .ctor(System.Security.Principal.IdentityReference, System.DirectoryServices.ActiveDirectoryRights, System.Security
.AccessControl.AccessControlType, System.Guid)

new System.DirectoryServices.ActiveDirectoryAccessRule ($self,‘ExtendedRight’,‘Deny’,‘ab721a53-1e2f-11d0-9819-00aa0040529b’)

ActiveDirectoryRights : ExtendedRight
InheritanceType       : None
ObjectType            : ab721a53-1e2f-11d0-9819-00aa0040529b
InheritedObjectType   : 00000000-0000-0000-0000-000000000000
ObjectFlags           : ObjectAceTypePresent
AccessControlType     : Deny
IdentityReference     : S-1-5-10
IsInherited           : False
InheritanceFlags      : None
PropagationFlags      : None

$SelfDeny = new-object System.DirectoryServices.ActiveDirectoryAccessRule ($self,‘ExtendedRight’,‘Deny’,‘ab721a53-1e2f-11d0-9819-00aa0040529b’)
$SelfAllow = new-object System.DirectoryServices.ActiveDirectoryAccessRule ($self,‘ExtendedRight’,‘Allow’,‘ab721a53-1e2f-11d0-9819-00aa0040529b’)
$EveryoneDeny = new-object System.DirectoryServices.ActiveDirectoryAccessRule ($Everyone,‘ExtendedRight’,‘Deny’,‘ab721a53-1e2f-11d0-9819-00aa0040529b’)
$EveryOneAllow = new-object System.DirectoryServices.ActiveDirectoryAccessRule ($Everyone,‘ExtendedRight’,‘Allow’,‘ab721a53-1e2f-11d0-9819-00aa0040529b’)

$mow.get_ObjectSecurity().AddAccessRule($selfDeny)
$mow.get_ObjectSecurity().AddAccessRule($EveryoneDeny)

$mow.CommitChanges()

#########################################

# Setting User must change password at next Logon

$user.pwdLastSet = -1

# Reading password last set value

$ds = New-Object directoryservices.directorysearcher($user)

MowPS>[datetime]::fromfiletime(($ds.findone().properties.pwdlastset)[0])

Monday, July 172006 5:07:05 PM

# Un-setting user must chage password at next logon
$user.pwdLastSet = 0

MowPS>$user.InvokeGet(‘PasswordLastChanged’)

***************************

MowPS>[datetime]::fromfiletime(($ds.findone().properties.pwdlastset)[0])

Next post I will go on with the script, but I’m still a bit thinging about it and as said did think the ACL material was worth a own “extra” part in this series.

In this entry I will show how you can use a recursive script to get the all members of a group, including the members that are in nested groups.

Note that the script will not handle groups with more as 1000 (W2K) or 1500 (W2K3) users,
as this is the maximum the Members property will enumerate.
If you have groups that are bigger you need to adapt the script to do a paged search for examples how to do this see :
Large AD queries in Monad

The script looks like this :# Function get-NestedMembers
# List the members of a group including all nested members of subgroups
# /\/\o\/\/ 2006

function get-NestedMembers ($group){ 
  if ($group.objectclass[1] -eq ‘group’) { 
        write-verbose “Group $($group.cn)” 
    $Group.member |% { 
      $de = new-object directoryservices.directoryentry(“LDAP://$_”
      if ($de.objectclass[1] -eq ‘group’) { 
        get-NestedMembers $de 
      } 
      Else { 
        $de.sAMAccountName 
      } 
    } 
  }
  Else {
    Throw “$group is not a group”
  } 

Note that I did add the groupname as a Write-Verbose, so it will only show in verbose mode and in verbose mode will only be displayed not passed on to the pipeline , also it will show users that are member of more groups as often as they get found, as show in the examples below you can use group or sort -unique to get a list of them or to only show the users / computers found in more groups once.

# get-NestedMembers usage examples :
 
# get a group

$group = new-object directoryservices.directoryentry(“LDAP://cn=MainGroup,OU=Groups,DC=mow,DC=Local”)

# Get all nested members

get-NestedMembers $group

User1
User2
User2
User3

# Show current verbose mode :

MowPS>$VerbosePreference
SilentlyContinue

# Enable Verbose Mode :

$VerbosePreference = ‘continue’

get-NestedMembers $group

VERBOSE: Group MainGroup
User1
User2
VERBOSE: Group SubGroup
User2
User3

# Disable Verbose Mode again :

$VerbosePreference = ‘SilentlyContinue’

# Group the output to get the doubles
 
get-NestedMembers $group | group

Count Name                      Group
—– —-                      —–
    1 User1                  {User1}
    2 User2                  {User2, User2}
    1 User3                  {User3}

# Use sort -Unique to get every user only once

get-NestedMembers $group | sort -Unique

User1
User2
User3

For this part 10 of this series, I’v made a GUI Active Directory Browser script in PowerShell. 

In the first post in this Active Directory series, PowerShell and Active Directory Part 1 , I showed how you could connect to the root of an Active Directory Domain,  

$root = New-Object System.DirectoryServices.DirectoryEntry 

Get a Sub OU : 

$mowOU = $root.get_Children().find(‘ou=mowOu’) 

Connect directly to an Active Directory Object :  

$mowOu = New-Object DirectoryServices.DirectoryEntry(LDAP://OU=MowOu,DC=mow,DC=local) 

And how to use them on the CommandLine, 

From there on, we amongst others,  

Listed and created AD Objects, as OU’s Users and Groups, Changed properties, Used methods, Exported and Imported/Created the Objects (Users) to and from a CSV file and did set ACL’s to AD Objects. 

But if you need to connect to a SubOU deep in the AD tree, it is hard to get at it this way, we or need a lot of Get_Children() and Find() Methods, or a long LDAP path. 

In next post we will so how to search for Objects in Active Directory using a DirectorySearcher, and how you can connect to the Active Directory Object from the results,that also will solve part of this problem. 

But most of the times  would also be handy to be be able to just browse through the AD Tree to the Object you need and then use it in PowerShell to perform some actions. 

I Made a Script for this in PowerShell  : BrowseActiveDirectory.ps1 

This Script will connect to the Root of the Domain or to custom root (a Subcontainer supplied as a DirectoryEntry Object, if you did follow the rest of the series, I hope this makes perfect sense ;-) ) and if this succeeds builds a Form that contains a TreeView Object, that you can use to browse to the AD object you  need, and then Returns it so you can use it in PowerShell. 

This Form, (as you might have seen in my Teaser), Looks like this (click picture to enlarge) 


 

In this case I walked to the OU : MowOU, I also used in former examples, when the Active Directory Browser will Startup you will only see the Root Object with the DN of the AD Object it represents. 

for performance reasons, not the Whole AD-tree will be read when starting up, but only when you select a Node in the TreeView, the Children of the AD object it presents get enumerated and there DN’s are added to the TreeView, we will use events from the TreeView to do this. 

When you use the Select Button or hit Enter, the DirectoryEntry will be retrieved and Passed back to the pipeline so  you can Catch it in the PowerShell console and put it into a variable for further use. 

I like this GUI form as a quick way to get to the Object I want in AD, it is much quicker then getting there from the commandline, or it also much easier to explore and look at the structure of the ActiveDirectory domain this way.  

As already mentioned in  PowerShell and Active Directory Part 8 (ACL’s) , I like to just use “loose lines of Code” or functions, that you just can just past into the Commandline, so most of my examples are given that way 

And I gave some more examples and explained a bit more about the difference with Scripts here :PowerShell, Has my Dell a dangerous battery ? ,  

This script I made Hybrid, you can start it directly or start it to load the function, I did add this function as a Switch Parameter, 

so Next to Using the script Directly like this : 

  

 

PoSH>$De = .\ActiveDirectoryBrowser.Ps1
PoSH>$De = fl *
PoSH>$De = .\ActiveDirectoryBrowser.Ps1
PoSH>$De | fl *
objectClass : {top, organizationalUnit}
ou : {MowSubOu}
distinguishedName : {OU=MowSubOu,OU=MowOu,DC=mow,DC=local}
instanceType : {4}
whenCreated : {6/26/2006 6:59:59 PM}
whenChanged : {6/26/2006 6:59:59 PM}
uSNCreated : {System.__ComObject}
uSNChanged : {System.__ComObject}
name : {MowSubOu}
objectGUID : {162 13 61 122 139 39 201 72 161 216 129 101 53 217 180 114}
objectCategory : {CN=Organizational-Unit,CN=Schema,CN=Configuration,DC=mow,DC=local}
nTSecurityDescriptor : {System.__ComObject}
# Use this as new Root 

PoSH>.\ActiveDirectoryBrowser.Ps1 $de 

You can also use the Browse-ActiveDirectory Function that does all the work in the Script, by  Loading it into your current session by dotSourcing the ActiveDirectoryBrowser.Ps1 script and providing the -loadOnly Switch 

then you can use the Browse-ActiveDirectory function interactively from then on, so you can also use this script in your profile 

 

If you use the -LoadOnly Switch but do not DotSource it the script will warn you like this : 

  

 

PoSH>.\ActiveDirectoryBrowser.Ps1 -l
WARNING: LoadOnly Switch is given but you also need to ‘dotsource’ the script to load the function in the global scope
To Start a script in the global scope (DotSource) put a dot and a space in front of path to the script
If the script is in the current directory this would look like this :
. .\ActiveDirectoryBrowser.Ps1
then :
The Browse-ActiveDirectory Function is loaded and can be used like this :
$de = Browse-ActiveDirectory

PoSH>. .\ActiveDirectoryBrowser.Ps1 -l
The Browse-ActiveDirectory Function is loaded and can be used like this :
$de = Browse-ActiveDirectory
 

If you use the script this way for loading the function, it also will Define the alias Bad for ease of use  

I hope this LoadOnly option example also shows a bit how you can use the different forms of starting things in PowerShell. 

The Script looks like this : 

  

  

# ActiveDirectoryBrowser.Ps1
#
# This script does show a GUI to browse ActiveDirectory in a Treeview
# and Returns the DirectoryEntry Selected for use in PowerShell
# or if LoadOnly Parameter is given it just loads the Browse-ActiveDirectory function, 
# and does set the alias bad, for loading the function for interactive use by dotsourcing the script
#
# /\/\o\/\/ 2006
#
http://mow001.blogspot.com

param (
  [directoryservices.directoryEntry]$root = (new-object system.directoryservices.directoryEntry),
  [Switch]$LoadOnly
)

# the Main function that can be loaded or gets started at the end of the script
 
Function Browse-ActiveDirectory {
  param ([directoryservices.directoryEntry]$root = (new-object system.directoryservices.directoryEntry))

  # Try to connect to the Domain root

  &{trap {throw “$($_)”};[void]$Root.get_Name()}

  # Make the form

  $form = new-object Windows.Forms.form   
  $form.Size = new-object System.Drawing.Size @(800,600)   
  $form.text = “/\/\o\/\/’s PowerShell ActiveDirectory Browser”  

  # Make TreeView to hold the Domain Tree

  $TV = new-object windows.forms.TreeView
  $TV.Location = new-object System.Drawing.Size(10,30)  
  $TV.size = new-object System.Drawing.Size(770,470)  
  $TV.Anchor = “top, left, right”    

  # Add the Button to close the form and return the selected DirectoryEntry
 
  $btnSelect = new-object System.Windows.Forms.Button 
  $btnSelect.text = “Select”
  $btnSelect.Location = new-object System.Drawing.Size(710,510)  
  $btnSelect.size = new-object System.Drawing.Size(70,30)  
  $btnSelect.Anchor = “Bottom, right”  

  # If Select button pressed set return value to Selected DirectoryEntry and close form

  $btnSelect.add_Click({
    $script:Return = new-object system.directoryservices.directoryEntry(“LDAP://$($TV.SelectedNode.text)”
    $form.close()
  })

  # Add Cancel button 

  $btnCancel = new-object System.Windows.Forms.Button 
  $btnCancel.text = “Cancel”
  $btnCancel.Location = new-object System.Drawing.Size(630,510)  
  $btnCancel.size = new-object System.Drawing.Size(70,30)  
  $btnCancel.Anchor = “Bottom, right”  

  # If cancel button is clicked set returnvalue to $False and close form

  $btnCancel.add_Click({$script:Return = $false ; $form.close()})

  # Create a TreeNode for the domain root found

  $TNRoot = new-object System.Windows.Forms.TreeNode(“Root”)
  $TNRoot.Name = $root.name
  $TNRoot.Text = $root.distinguishedName
  $TNRoot.tag = “NotEnumerated”

  # First time a Node is Selected, enumerate the Children of the selected DirectoryEntry

  $TV.add_AfterSelect({
    if ($this.SelectedNode.tag -eq “NotEnumerated”) {

      $de = new-object system.directoryservices.directoryEntry(“LDAP://$($this.SelectedNode.text)”)

      # Add all Children found as Sub Nodes to the selected TreeNode

      $de.get_Children() | 
      foreach {
        $TN = new-object System.Windows.Forms.TreeNode
        $TN.Name = $_.name
        $TN.Text = $_.distinguishedName
        $TN.tag = “NotEnumerated”
        $this.SelectedNode.Nodes.Add($TN)
      }

      # Set tag to show this node is already enumerated
 
      $this.SelectedNode.tag = “Enumerated”
    }
  })

  # Add the RootNode to the Treeview

  [void]$TV.Nodes.Add($TNRoot)

  # Add the Controls to the Form
  
  $form.Controls.Add($TV)
  $form.Controls.Add($btnSelect ) 
  $form.Controls.Add($btnCancel )

  # Set the Select Button as the Default
 
  $form.AcceptButton =  $btnSelect
     
  $Form.Add_Shown({$form.Activate()})   
  [void]$form.showdialog() 

  # Return selected DirectoryEntry or $false as Cancel Button is Used
  Return $script:Return
}

# If used as a script start the function

Set-PSDebug -Strict:$false #  Otherwise Checking the Switch parmeter does fail (Bug ?)

if ($LoadOnly.IsPresent) {

  # Only load the Function for interactive use

  if (-not $MyInvocation.line.StartsWith(‘. ‘)) {
    Write-Warning “LoadOnly Switch is given but you also need to ‘dotsource’ the script to load the function in the global scope”
    Write-Host “To Start a script in the global scope (dotsource) put a dot and a space in front of path to the script”
    Write-Host “If the script is in the current directory this would look like this :”
    Write-Host “. .\ActiveDirectoryBrowser.Ps1″
    Write-Host “then :”
  }
  Write-Host “The Browse-ActiveDirectory Function is loaded and can be used like this :”
  Write-Host ‘$de = Browse-ActiveDirectory’
  Set-alias bad Browse-ActiveDirectory
}
Else {

  # start Function

  . Browse-ActiveDirectory $root

}  

  

  

Most of this Code is straight forward I think with the # comments( if you followed the AD series, and have seen some of my other PowerShell GUI Examples) , just remember that the add_* methods are used to add scripts to events from the objects on the Form. 

here some remark by the main points of interest in the script (sorry Numbers Gone) : 

Line 24 : &{trap {throw “$($_)”};[void]$Root.get_Name()}  

This line does test the ActiveDirectory Connection by getting the name Property and use a Trap block to catch it and throwing the Error again so the script will stop if this fails.  

Line 49,64 :$btnCancel.add_Click({$script:Return = $false ; $form.close()}) 

The add_Click methods handle the event when I button gets clicked you can give this funtion a Delegate ScriptBlock that gets executed every time the button get clicked. 

Line 75-95 : $TV.add_AfterSelect({  

This whole Scriptblock is also an eventhandler delegate, this one from the Treeview object and it is called every time a TreeNode is selected, 

you can see there is a variable with the name $This, this will contain the TreeView object so we can get the treenode that is selected at that moment from that  

Line 76 : if ($this.SelectedNode.tag -eq “NotEnumerated”) 

we check if we already did get the children of this node, otherwise we will collect them and add SubNodes to the TreeView for them. 

Line 109 : $form.AcceptButton =  $btnSelect 

Make The Select Button the Default so it will get a click event when [Enter] is pressed 

Line 111 : $Form.Add_Shown({$form.Activate()}) 

Used to give the form focus 

Line 120 : The logic to make the script “Hybrid” 

For more info about the AD parts see the rest of the series,  

For more info about building GUI’s and placing and using Form elements on them in PowerShell See my Other GUI examples in former posts. 

Using a DataGrid : 

PowerShell out-DataGrid update and more DataSet utilities 

Using a Property Grid (Object Viewer) 

PowerShell-out-propertygrid-msh-view.html 

about Focus the Form problem (updated version this script) 

Simple Show-Image function for Monad 

Adding menus etc , 

MSH Concentration (scripting games part 4) 

MSH Minesweeper GUI game 

Hosting PowerShell in it. 

Hosting an MSH runspace from Monad 

Use For EventWatcher workaround (STA Thread ), and how to use Popup balloons. 

MSH directory watcher with popup-balloon 

There are a bit more posts to find, if you search on GUI on my blog. 

*Edit* as the lineNumbers do stay if you copy the Code Above and pasting it, So I’m posting the code another time without LineNumbers 

In this post Move and Renaming AD objects, still not about Searching AD as I did say before using the DirectorySearcher Object, but as a lot of my older posts and examples about  already use it, If you did read the read the other parts of this AD series, you should be able understand those example scripts to get much information from that older scripts about searching AD going on from the examples, and pasting in lines of code from the examples into the interactive Shell .  See for a complete list, of all the parts in this series, and the other examples and scripts using the DirectorySearcher. The excellent PowerShell Links directory Dance2Die maintains on Del.icio.us : PowerShell (Note the RSS feed !! )in the Active Directory Subdirectory: del.icio.us PowerShell AD links(if can you make a get-Aduser ($SamAccountName){} from that using a DirectorySearcher as an example, please leave it in the comments ;-) so in this post renaming and moving objects as that was not discussed yet : Yesterday night,  I got a question on how to rename a user in PoSH on IRC (IRC.freeNode.net #PowerShell).I could not come up with a direct answer at the time, but did remember from VbScript that you could use a method on the OU, I did a quick lookup, Microsoft Windows 2000 Scripting Guide – Moving and Renaming User and came to this :PoSH>$MowOu = C:\PowerShell\ActiveDirectoryBrowser.Ps1PoSH>$mowOU distinguishedName
—————–
{OU=MowOu,DC=mow,DC=local}
PoSH>$mowou.get_Children() distinguishedName
—————–
{CN=$_,OU=MowOu,DC=mow,DC=local}
{CN=foo,OU=MowOu,DC=mow,DC=local}
{CN=foobar,OU=MowOu,DC=mow,DC=local}
{CN=Ken Myer,OU=MowOu,DC=mow,DC=local}
{CN=mow,OU=MowOu,DC=mow,DC=local}
{CN=Mow2,OU=MowOu,DC=mow,DC=local}
{OU=MowSubOu,OU=MowOu,DC=mow,DC=local}
{CN=NewUser0003,OU=MowOu,DC=mow,DC=local}
{CN=NewUser0010,OU=MowOu,DC=mow,DC=local}
{CN=NewUser0011,OU=MowOu,DC=mow,DC=local}
{CN=NewUser0267,OU=MowOu,DC=mow,DC=local}
{CN=TestGroup,OU=MowOu,DC=mow,DC=local}
PoSH>$mowOU.invoke(‘MoveHere’,’LDAP://CN=mow,OU=MowOu,DC=mow,DC=local’,’cn=mowMoved’) distinguishedName
—————–
{CN=mowMoved,OU=MowOu,DC=mow,DC=local}
  After I came home from work today and found back the PowerShell console as I left it last night, I did a get-member again, and did see that I had missed the most obvious solution last night, using the Methods on the DirectoryObject. PoSH>$mowOU | gm -MemberType method TypeName: System.DirectoryServices.DirectoryEntry Name MemberType Definition
—- ———- ———-
add_Disposed Method System.Void add_Disposed(EventHandler value)
Close Method System.Void Close()
CommitChanges Method System.Void CommitChanges()
CopyTo Method System.DirectoryServices.DirectoryEntry CopyTo(DirectoryEntry newParent), Syste…
CreateObjRef Method System.Runtime.Remoting.ObjRef CreateObjRef(Type requestedType)
DeleteTree Method System.Void DeleteTree()
Dispose Method System.Void Dispose()
Equals Method System.Boolean Equals(Object obj)
get_AuthenticationType Method System.DirectoryServices.AuthenticationTypes get_AuthenticationType()
get_Children Method System.DirectoryServices.DirectoryEntries get_Children()
get_Container Method System.ComponentModel.IContainer get_Container()
get_Guid Method System.Guid get_Guid()
get_Name Method System.String get_Name()
get_NativeGuid Method System.String get_NativeGuid()
get_NativeObject Method System.Object get_NativeObject()
get_ObjectSecurity Method System.DirectoryServices.ActiveDirectorySecurity get_ObjectSecurity()
get_Options Method System.DirectoryServices.DirectoryEntryConfiguration get_Options()
get_Parent Method System.DirectoryServices.DirectoryEntry get_Parent()
get_Path Method System.String get_Path()
get_Properties Method System.DirectoryServices.PropertyCollection get_Properties()
get_SchemaClassName Method System.String get_SchemaClassName()
get_SchemaEntry Method System.DirectoryServices.DirectoryEntry get_SchemaEntry()
get_Site Method System.ComponentModel.ISite get_Site()
get_UsePropertyCache Method System.Boolean get_UsePropertyCache()
get_Username Method System.String get_Username()
GetHashCode Method System.Int32 GetHashCode()
GetLifetimeService Method System.Object GetLifetimeService()
GetType Method System.Type GetType()
InitializeLifetimeService Method System.Object InitializeLifetimeService()
Invoke Method System.Object Invoke(String methodName, Params Object[] args)
InvokeGet Method System.Object InvokeGet(String propertyName)
InvokeSet Method System.Void InvokeSet(String propertyName, Params Object[] args)
MoveTo Method System.Void MoveTo(DirectoryEntry newParent), System.Void MoveTo(DirectoryEntry…
RefreshCache Method System.Void RefreshCache(), System.Void RefreshCache(String[] propertyNames)
remove_Disposed Method System.Void remove_Disposed(EventHandler value)
Rename Method System.Void Rename(String newName)

set_AuthenticationType Method System.Void set_AuthenticationType(AuthenticationTypes value)
set_ObjectSecurity Method System.Void set_ObjectSecurity(ActiveDirectorySecurity value)
set_Password Method System.Void set_Password(String value)
set_Path Method System.Void set_Path(String value)
set_Site Method System.Void set_Site(ISite value)
set_UsePropertyCache Method System.Void set_UsePropertyCache(Boolean value)
set_Username Method System.Void set_Username(String value)
ToString Method System.String ToString()
So you also can do this : # Using the DirectoryEntry Rename method PoSH>$mowOU.get_Children() |? {$_.cn -match ‘mowMoved’} distinguishedName
—————–
{CN=mowMoved,OU=MowOu,DC=mow,DC=local}
PoSH>$MowMoved = $mowOU.get_Children() |? {$_.cn -match ‘mowm’} PoSH>$MowMoved distinguishedName
—————–
{CN=mowMoved,OU=MowOu,DC=mow,DC=local}
PoSH>$MowMoved.rename(‘cn=mow’)
PoSH>$MowMoved
distinguishedName
—————–
{CN=mow,OU=MowOu,DC=mow,DC=local}
 # Connect to RootDSE and go to user by setting a new path using Set_PathPoSH>$mow = new-object system.directoryservices.directoryEntry
PoSH>$mow
distinguishedName
—————–
{DC=mow,DC=local}
PoSH>$mow.set_path(‘LDAP://CN=mow,OU=MowOu,DC=mow,DC=local’)
PoSH>$mow
distinguishedName
—————–
{CN=mow,OU=MowOu,DC=mow,DC=local}
You can see from this that it is handy that you can use the methods you are used to (or vaguely remember) from VbScript. And you can use the methods from the .NET Wrapper Object ( you can learn about using Get-Member ). Ok, Ok the CmdLets are for V2, (but I did hear some rumors about some extra functionality for AD as well as for WMI in RC2 .), but working in the Interactive Shell console like this trying things interactively , combining Old and discovered knowledge, having much less looking up of Information and templates for admin task, using SDK DLL’s (MOM 2005,SMS2003)gives you that kind of a productivity boost compared to working in VbScript ( a guess for me it’s 10x and sometimes (for on the fly work ) much more (could be days ), as against doing the same in VbScript, and 2 or 3 times for CMD.EXE and commandline tool usage where applicable ) That sometimes I really feel like flying in PowerShell ;-)  and we can make our own functions with ease from the commands also :   PoSH>function get-ADObject ($Path) {new-object system.directoryservices.directoryEntry($path)}
PoSH>get-ADObject ‘LDAP://CN=mow,OU=MowOu,DC=mow,DC=local’
distinguishedName
—————–
{CN=mow,OU=MowOu,DC=mow,DC=local}
   and with the easy of making GUI Forms in PowerShell see  PowerShell : Active Directory Part 10 (AD Browser) , for exporing the AD tree and visual selecting an AD object for use in PowerShell, that also makes it easy to get to AD objects for interactive tasks. Not everything is perfect yet (Exception Handling, for in scripts,Remoting etc.), but I would  not be able to live without it for my dayly work allready. If you followed this series, and  / or used PowerShell for AD management, compared to how you used to do you think it gives you a productivity boost, or will be able to ? I would be glad to hear what you think about this , please leave a comment about that also (Ok, enough begging, but my read / comment rate is low ;-) )  

About these ads
Standard

One thought on “PowerShell Active Directory Series

  1. Pingback: How To Fix Hresult 0x8007200a Errors - Windows Vista, Windows 7 & 8

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s