Showing posts with label Active Directory. Show all posts
Showing posts with label Active Directory. Show all posts

May 13, 2010

Get members of an active directory group including members of nested groups

Copy this script and save it as get-GroupNestedMembers.ps1

#
# .SYNOPSIS 
#   Get members of an active directory group including members
#   of nested groups
#	
# .DESCRIPTION
#   Search Active Dircetory to find a group and get members of
#   that group including members of nested groups. Return
#   an object.
#
#   Author   : Jean-Pierre.Paradis@fsa.ulaval.ca
#   Date     : May 13, mars 2010
#   Version  : 1.00
#   Language : PowerShell 2.0
#    
# .PARAMETER GroupName
#   Name of the group (mandatory)
#
# .PARAMETER OU
#   Distinguished name of an Organizational Unit (OU) to search (optional)
#
# .LINK
#   Inspired by http://gallery.technet.microsoft.com/ScriptCenter/en-us/1228cdfa-9c04-4bc7-a016-11b492c704d2
#   from Trevor Hayman
#
# .EXAMPLE
#   C:\PS> .\get-GroupNestedMembers.ps1 -groupname "mygroup"
# .EXAMPLE
#   C:\PS> .\get-GroupNestedMembers.ps1 -groupname "mygroup" -ou "OU=Experimentation,DC=mydomain,DC=com"
# .EXAMPLE
#   C:\PS> .\get-GroupNestedMembers.ps1 -groupname "mygroup" -verbose
# .EXAMPLE
#   C:\PS> .\get-GroupNestedMembers.ps1 -groupname "mygroup" | Out-GridView


#REQUIRES -version 2.0

param (
	[parameter(
	Mandatory=$true,
	ValueFromPipeline=$True)]
	[String]$GroupName,
	
	[parameter(
	Mandatory=$false)]
	[String]$OU=""

	) 

Set-StrictMode -Version 2.0

Function Add-GroupMembers {
# .SYNOPSIS 
#   Add member of -GroupDistinguishedName to $Script:ColGroupMembers
	param (
		[parameter(
		Mandatory=$true)]
		[String]$GroupDistinguishedName
		)
	 $objGroup = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$GroupDistinguishedName")
	 $MemberList = $objGroup.member
	 ForEach ($member in $MemberList) {
	 	$objMember = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$member")
		if ($objMember.objectCategory.Value.Contains("Group")) {
			Add-GroupMembers -GroupDistinguishedName $objMember.DistinguishedName
			}
		Else {
			$Script:ColGroupMembers.add((New-Object psobject -Property @{Name=[string]$objMember.name;DisplayName=[string]$objMember.DisplayName;DistinguishedName=[string]$objMember.DistinguishedName;ImmediateParent=[string]$objGroup.name}))
			}
	 	}
}

# Setup AD search root
if ($OU -ne "") {
	Write-Verbose "Searching from $OU"
	$root=[System.DirectoryServices.DirectoryEntry] "LDAP://$OU"
	}
Else {
	$root=[string]""
	}
$search = [ADSISearcher] $root

# Find the group
$search.Filter = "(&(objectCategory=Group)(cn=$GroupName))"
$result = $search.FindOne()
If ($result -eq $null) {
	Write-Error "Group '$groupname' not found !"
	exit
	}
Write-Verbose "Found group $($result.Properties.distinguishedname)"

# Create a collection to store de results
$psobjectStrongName = (New-Object psobject).psobject.GetType().AssemblyQualifiedName
$Script:ColGroupMembers = New-Object "System.Collections.ObjectModel.Collection``1[[$psobjectStrongName]]"

Add-GroupMembers -GroupDistinguishedName $result.Properties.distinguishedname
Write-verbose "Found $($ColGroupMembers.count) member(s)"
return ($ColGroupMembers|Sort-Object 'Name')

April 16, 2010

Create users in Active Directory from a CVS file

[update august 23th, 2010] This is a new version (1.10) that allows you put an ‘#’ in the ‘password’ field to be able to update properties of an existing user without modifying his password.

#
# .SYNOPSIS 
#   Create users in Active Directory from a CVS file (';' delimited).
#	
# .DESCRIPTION
#   The CSV file used to create the users must have a special format.
#   The first line of the CVS file must contain columns names.
#   Each column name must correspond exactly to an attribute name of 
#   a user object in the Active Directory.
#   All Active Directory attributes containing a text value can be used.
#  
#   The following columns (attributes) are mandatory :
#
#      Name               If this field contain a '#', the line will
#                         ingnored (commented).
#      OU
#      Password           If this filed contain # '#', the password
#                         will not be set. Of course the user must
#                         already exist.
#
#   The follwing attributes (columns) cannot be used, they are automatically
#   generated by the script :
#
#      sAMAccountName
#      userPrincipalName
#
#   The following special columns (attributes) are handle by the script :
#
#      OU                  Organizational Unit containing the user to be created.
#                          The distingushedName of the domain will be appended.
#      sAMAccountName      Automicically set to the value of the Name column
#      userPrincipalName   Automicically set to the value of the Name column
#                          plus the canonicl name of the domain
#                         (ex: user1@domain.com)
#      MemberOf            List of group names separeted by a comma (,) do not
#                          specified the full distingushedName or the group
#                          only the name, the script will do the rest.
#      AccountExpires      Set the expiration date of the new account
#
#   If an attribute of the new user cannot be set, the new user account will
#   be left disabled.
#
#   If the user account already exists, the script will not end and will
#   try to set the attributes of the accounts.
#
#   Here's a sample CSV file :
#   Name;OU;Password;displayName;mail;description;AccountExpires;memberOf
#   PdelphiC1;ou=UtilisateursExternes;!42mj428;My First Poweshell User;chico@hotmail.com ;This is a test;2010-09-15;group 1,group 2
#   PdelphiC2;ou=UtilisateursExternes;$93me934;My second PowerShell User;binou@hotmail.com ;This is a test;2010-09-15;group 3
#   # This is a sample input file for Import-Users.ps1
#
#   Author   : Jean-Pierre.Paradis@fsa.ulval.ca
#   Date     : august 23,  2010
#   Version  : 1.10
#   Language : PowerShell 2.0
#    
# .PARAMETER CSVInputFile
#   Input file name.
#		
# .EXAMPLE
#  C:\PS> .\Import-users.ps1 -CSVInputFile "users.cvs"
#
# .LINK 
#  Inspire by Don Jones and Jeffrey Hicks in 'Windows powershell 2.0 TFM' from Sapien Press
#

#REQUIRES -version 2.0

param (
	[parameter(
	Mandatory=$true)]
	[String[]]$CSVInputFile
	) 
	
Set-StrictMode -Version 2.0

# Script parameters
# $CSVInputFile="newusers.csv"
$Delimiter=";"
$DirectoryServicesCOMException_ENTRY_EXISTS=-2147019886

function get-scriptdirectory {

# .SYNOPSIS 
# 	Return the current script directory path, compatible with PrimalScript 2009
# 	Equivalent to VBscript fso.GetParentFolderName(WScript.ScriptFullName)
# 	Requires PowerShell 2.0
#    
# .DESCRIPTION
#	Author   : Jean-Pierre.Paradis@fsa.ulaval.ca
#	Date     : March 31, 2010
#	Version  : 1.01
#
# .LINK 
# 	http://blog.sapien.com/index.php/2009/09/02/powershell-hosting-and-myinvocation/

    if (Test-Path variable:\hostinvocation) 
    	{$FullPath=$hostinvocation.MyCommand.Path}
    Else {
   		$FullPath=(get-variable myinvocation -scope script).value.Mycommand.Definition }  	
	if (Test-Path $FullPath) {
    	return (Split-Path $FullPath) 
		}
    Else {
		$FullPath=(Get-Location).path
		Write-Warning ("Get-ScriptDirectory: Powershell Host <" + $Host.name + "> may not be compatible with this function, the current directory <" + $FullPath + "> will be used.")
		return $FullPath
		}
}

function get-GroupDN {
# .SYNOPSIS 
# 	Return an Active Directory group distinguished name
param (
	[parameter(
	Mandatory=$true)]
	[String[]]$GroupShortName
	)
	
	$AdSearch = [ADSISearcher] ""
	$AdSearch.Filter = "(&(objectCategory=group)(cn=$($GroupShortName)))"
	$AdResult = $ADSearch.FindOne()
	if ($AdResult -ne $null) {
		return $AdResult.properties.distinguishedname
		}
	Else {
		Write-Warning ("get-GroupDN: Can't find group named <$($GroupShortName)>")
		return $null
		}
}


function add-UserToGroups {
# .SYNOPSIS 
# 	Add a user to an array of groups
param (
	[parameter(
	Mandatory=$true)]
	[String]$UserDistinguishedName,

	[parameter(
	Mandatory=$True)]
	[System.Array]$Groups
	
	)
	
	Foreach ($GroupName in $Groups) {
		$GroupDN = get-GroupDN($GroupName)
		if ($GroupDN -ne $null) {
			Write-host "   Adding to group '$($groupname)' ..."
			$GroupObj=[adsi]("LDAP://"+$GroupDN)
			$GroupObj.member.add($UserDistinguishedName) >$null
			
			Try {
				$GroupObj.SetInfo()
				}
			Catch [System.DirectoryServices.DirectoryServicesCOMException] {
				If ($_.exception.ErrorCode -eq $DirectoryServicesCOMException_ENTRY_EXISTS) {
					Write-Warning "The user is already a member."
					}
				else {
					Throw $_
					}
				}
			}
	}
}


# Read the data file and filter any commented line
$CSVInputFileFullPath = (get-scriptdirectory($CSVInputFile)) + "\" + $CSVInputFile
$imported=Import-Csv $CSVInputFileFullPath -Delimiter $Delimiter | where {$_.name -notlike '#*'}

# retrieve list of csv column headings
# Each column heading should correspond to an ADSI user property name
$properties=$imported | Get-Member -type noteproperty | `
where {$_.name -ne "OU" -and $_.name -ne "Password" `
-and $_.name -ne "Name" -and $_.name -ne "sAMAccountName" `
-and $_.name -ne "userPrincipalName"}

# Get the domain canonicalName (mydomain.com) & distinguishedname
$rootDomain=[ADSI]""
$rootDomain.RefreshCache(@("canonicalName"))
$rootDomainCanonicalName=($rootDomain.get("canonicalName") -replace "/","")
$rootDomainDistinguishedName=$rootDomain.distinguishedName

# Loop throuth the data
foreach ($user in $imported) {
 
 		# Create the account
        $UserAlreadyExist=$false
		$OUDistinguishedName = $user.OU+","+$rootDomainDistinguishedName
		Write-Host "Creating User '$($user.Name)' in '$($OUDistinguishedName)' ..."
		[ADSI]$OU="LDAP://"+$OUDistinguishedName
		$newUser=$OU.Create("user","CN="+$user.Name)
		$newUser.Put("sAMAccountName",$user.Name)
		
		# Get the domain canonicalName (mydomain.com)
		$rootDomain=[ADSI]""
		$rootDomain.RefreshCache(@("canonicalName"))
		# Set userPrincipalName
		$newUserPrincipalName=$user.Name + "@" + $rootDomainCanonicalName
		$newUser.Put("userPrincipalName",$newUserPrincipalName)
		
		# commit creation to Active Directory
		Try {
			$newUser.SetInfo()
			}
		Catch [System.DirectoryServices.DirectoryServicesCOMException] {
			If ($_.exception.ErrorCode -eq $DirectoryServicesCOMException_ENTRY_EXISTS) {
				Write-Warning "The object already exist, will try to reset properties."
				$newUser=[adsi]("LDAP://"+"CN="+$user.Name+","+$OUDistinguishedName)
                $UserAlreadyExist=$true
				}
			else {
				Throw $_
				}
			}
			
		# set a password 
        
        If ($user.password -notlike '#*') {
		  Write-Host ("   Setting password")
		  $newUser.SetPassword($user.Password)
		  $newUser.SetInfo()
        }

		# set additional properties
		foreach ($prop in $properties) {
		$value=$user.($prop.name)
		if (($value -ne $Null) -and ($value.length -gt 0)) {
			Switch ($prop.name ) {
				"MemberOf" {
					# Write-Host ("   Adding to groups $($Value)")
					add-UserToGroups $newUser.distinguishedname @($value -split ",")
					}
				"AccountExpires" {
					$newAccountExpires = ($value -as [datetime])
					if ($newAccountExpires -ne $null) {
						Write-Host ("   Setting '$($prop.name)' to $($newAccountExpires)")
						$newUser.InvokeSet("AccountExpirationDate",$newAccountExpires)
						}
					Else {
						Write-Warning "   Can't convert '$($value)' to acceptable format for AccountExpires"
						}
					}
				default {
					#only set properties that have values
					Write-Host ("   Setting '$($prop.name)' to '$($value)'")
					$newUser.put($prop.name,$value)
					}
				}
			}
		}
		$newUser.SetInfo()
		
		# Activate the account
        If ($UserAlreadyExist -ne $True) {
          Write-Host ("   Enabling account")
		  $newUser.Invokeset("AccountDisabled", "False")
		  $newUser.SetInfo()
        }
 }

April 13, 2010

How to get or set the AccountExpires attribute

This not as easy as one might think. the active Directory is not storing dates as a normal [datetime],it use a special a 64bit interger call an IADsLargeInterger.

This mean that you can’t simply use a $user.accountExpires property to get or set the value, this will return a System.__ComObject

To get the AccountExpires property the easiest way (I think), is to use the DirectorySearcher [ADSISearcher] . This will not return a normal DirectoryEntry but a SearchResult data type. The difference is that the accountExpires property is return as an int64 instead of a IADsLargeInterger (don’t ask me why). An int64 is much easier to handle, here’s how to do it :

$search = [ADSISearcher] "" 
$search.Filter = "(cn=testUser)" 
$user = $search.FindOne()  
[datetime]::fromfiletime($user.properties.accountexpires[0])

Of course you can’t set the  AccoutExpires with a SearchResult data type. But you can use a special invokeSet on a DirectoryEntry that seems to convert a [datetime] to the correct format :

$user=[adsi]"LDAP://cn=testUser,dc=mycompany,dc=com"
$newAccountExpires=(Get-Date).addMonths(1)
$User.InvokeSet("AccountExpirationDate",$newAccountExpires)

You can also try to use an InvokeGet("AccountExpirationDate") method on a DirectoryEntry but it will throw an error if the AccountExpires attribute is not set (E_FAIL).

Update october 28th, 2013

With Powershell 2.0 and greater you can use the ConvertLargeIntegerToInt64 method :

$user=[adsi]"LDAP://cn=testUser,dc=mycompany,dc=com"
[datetime]::fromfiletime($user.ConvertLargeIntegerToInt64($user.properties.lastlogon[0]))

How to get an attribute not in the ADSI property cache ?

To reduce network traffic ADSI implement a property cache for Active Directory Objects. When an ADSI object is instantiated (created), the cache is populated with object’s attributes stored in Active Directory.

To reduce the amount of data managed by the cache only the most frequently used attributes are kept in the cache. If you want to retrieve an attribute (property) that is not in the cache, let say for example the canonicalName of the domain (mydomain.com) you have to call the RefreshCache  method like this :

$root=[ADSI]""
$root.RefreshCache(@("canonicalName"))
$Root.get("canonicalName")

How to trap an “Object already exists” error setting Active Directory properties

Sometimes when you create a new user, a new group or add a new member to a group you don’t want you script to end if the object already exist or the user is already a member.

It’s the setinfo() method that return this exception (error). So simply add a Try Catch  before and after your setinfo() like this :

Try {
	$newUser.SetInfo()
	}
Catch [System.DirectoryServices.DirectoryServicesCOMException] {
	If ($_.exception.ErrorCode -eq -2147019886) {
		Write-Warning "The object already exists."
		}
	else {
		Throw $_
		}
	}

April 7, 2010

Is Active Directory properties names case-sensitive ?

Yes.

$result.properties.displayname is different form $result.properties.DisplayName

What’s the syntax to build an Active Directory search filter ?

http://msdn.microsoft.com/en-us/library/aa746475(VS.85).aspx

How to search for an object in Active Directory in a particular OU

$root = [System.DirectoryServices.DirectoryEntry] "LDAP://OU=Employes,DC=dept,DC=company,DC=com"
$search = [ADSISearcher] $root
$search.Filter = "(cn=myUserName)"
$result = $search.FindOne()
$result.properties

How to search for an object in Active Directory ?

$search = [ADSISearcher] ""
$search.Filter = "(cn=myUserName)"
$result = $search.FindOne()
$result.properties