Tuesday, June 21, 2016

Tips for AD group membership managment in powershell

Managing large groups can fail due to limits in Active Directory Web Services when too many members are in a group.

Fails: Get-adgroupmember "LargeGroup"
       error:  Get-ADGroupMember : The size limit for this request was exceeded

Works:  Add-adgroupmember and remove-adgroupmember

Work Around: get-adgroup "LargeGroup" -properties members | select -expand members

This will get the distinguishednames of all members as an array.

-----------------------------------------------

Piping groups or users into a group membership cmdlet to change the group memberships.

1) When you are piping groups into a cmdlet where the user(s) are static.  Pipe to Add-ADGroupMember.
    Ex:  get-adgroup -filter {name -like "HelpDesk*"}| add-adgroupmember -members $userdn

2) When you are piping users into a cmdlet where the group(s) are static.  Pipe to Add-ADPrincipalGroupMembership
    Ex: get-aduser bob | Add-ADPrincipalGroupMembership -memberof $groupdn

NOTE: Add-ADPrincipalGroupMembership will generate successful security audit events (Directory Service Change) for the addition of the group member, even if they were already a member of the group

-----------------------------------------------

When using Add-ADGroupMember with an array of members, if any of them are part of the group already, the whole operation will fail.  Its best to try adding one at a time.

Thursday, June 16, 2016

Piping get-aduser output through several custom powershell functions

For people who write scripts to process large amounts of AD objects, you may find that use of pipelines will be more memory efficient that variables and foreach. In one case, I was working through a problem with a script in powershell v2, where lots of strange failures were occurring, such as failing to assign values to a variable. A statement like: $a = 1, may just randomly not work. As the script was doing some heavy processing of large numbers of objects, I assumed memory consumption was the problem. I wanted to convert the script to use of pipeline. Since the script had several functions that handled different aspects of what was needed, I thought it would be good to try multiple pipelines. In the end, the change to pipelines fixed all the failures in the script.

What I wanted:

Take an OU, run get-aduser on the OU -> Pipe to an analysis function to check password expiration for different types of accounts and password policies, then decide if an email notice needed to be sent -> Pipe (if needed) to an email function -> Pipe the results of all of the above to logging function.

At each stage, different bits of calculated data or additional properties needed to be added to the original get-aduser object. This was possible by using custom PSObjects after the initial analysis function. The basics of the code is below:

function process-OU { 
 param(  
  [parameter(mandatory=$true)][string]$searchbase, 
  [string]$type="standard"
 )
 Get-ADUser -Filter {(enabled -eq $True) -and (mail -like "*") } `
      -SearchBase $SearchBase `
      -Properties mail, PasswordLastSet, sn, PasswordNeverExpires | 
           analyze-user -type $type |email-user |log-result
}

function Analyze-User{
 [CmdletBinding()]
 param (
  [Parameter(Mandatory=$True,ValueFromPipeline=$True)]
    [Microsoft.ActiveDirectory.Management.ADAccount]$user,
  [string]$Type
 )
 begin {}
 process {
  #do some analysis and decide if you want to 
                #continue with write-output $user
  #
  #Add any additional pieces of information to the user object with
  #   add-member -input $user -force NoteProperty Expired $False
  if ($proceedtoEmail) { write-output $user } 
 }
}

function Email-User {
 [CmdletBinding()]
      Param(  
        [Parameter(Mandatory=$True,ValueFromPipeline=$True)]
        [PSobject]$emailuser  
      )
    #Notice the parameter type is a generic 
    #[psobject] as it is no long conforming 
    #to the [Microsoft.ActiveDirectory.Management.ADAccount] type
 Begin{}
 Process {
  #handle email creation and sending.  
                #Check if it was sent without error, 
                #add email status as another property
 }
}

function log-result {
 [CmdletBinding()]
         Param(   
            [Parameter(Mandatory=$True,ValueFromPipeline=$True)]
              [PSObject]$user   
        )
 begin {}
 process {
  #do some logging here
 }
}


process-OU -searchbase "ou=myusers,dc=contoso,dc=com" -type "regular"