Scripting Games May 2016 AD Puzzle

I love working in AD (Active Directory) with PowerShell. I find that I have had to really dig in to learn some of the syntax nuances that you need to understand to really mine data and change configurations within Active Directory. This puzzle reflects the kind of situation that people have to deal with in PowerShell everyday. I am interested to see what kinds of approaches each of you will take, this is a real chance to learn more of the diversity of methods that can be used in Active Directory with PowerShell.

This month Bartek Bielawski has submitted two puzzles, I am going to post the beginner to medium one first and then the advanced one next month. This is going to be a real learning opportunity. Keep the puzzles coming in, Mike F. Robbinson has submitted one recently too, so you can look forward to that in a couple of months.

Here we go:
During an internal IT audit of rights on your file server it was discovered that certain group had rights to the share used by finance and HR with sensitive data and the main question is: who was able to access these files because of that. When it happens you are attending a conference (surprise, surprise) and can’t really do anything remotely. That doesn’t stop your boss from calling you and asking for help. All she wants is a list of all users that are members of that group. The problem is that this group suffers from snow-ball effect and has multiple nested groups, that contain nested groups, that contain nested…
You respond with “use Get-ADGroupMember -Recursive” but your boss complains, that when she tried to use it, she just got some red text on her screen with information, that common delete is not recognized. You roll your eyes and eventually decide to write a short script and send it over e-mail. Luckily, you have sandbox domain controller running on your laptop, so testing your code is not that difficult. As you are in the middle of an interesting talk, you try to make it as simple and minimalistic as possible. You also decide not to try any other tools that require something to be installed on a computer running the code. One call from the boss is enough.

Design goals:
- Solution has to be quick. Don’t waste time on producing nice, informative error messages.
Your boss won’t read them anyway
- Try to use a solution that requires least testing possible
- Writing simple function could be nice, but if you manage to get it done in two lines, or even one – just do it
- You are limited to build-in functionality only.

Posted in:
About the Author

i255d

I am working on building a career with PowerShell. I am always learning, looking forward to what I will learn from you.

23 Comments

  1. You're right. It fails if a user has a different PrimaryGroup than the 'Domain User'. But you can write a recursive function that does the job.

    function Get-NestedADGroupMember ($group) {

    $items = Get-ADGroupMember $group

    foreach ($item in $items) {
    try{
    if ($item.objectclass -like "user") {
    # only get users back
    $item
    }

    if (Get-ADGroup $item.samaccountname) {
    Get-NestedADGroupMember $item.samaccountname
    }
    } catch{}

    }
    }

    • Thats a good replacement solution as a generel solution.
      The question is if this is simple enough and whether or not the script should be able to run without the activedirectory module (see my post 10 minutes earlier).

  2. Get-ADGroupMember "Name of Group" -Recursive -ea silentlycontinue|Select -ExpandProperty SamAccountName|Get-ADUser -Properties Mail,Enabled -ea SilentlyContinue|sort Name|select Name,Mail,Enabled

  3. [..] but your boss complains, that when she tried to use it, she just got some red text on her screen with information, that common delete is not recognized

    Maybe the boss tried to reproduce the PowerShell feedback: "The term 'Get-ADGroupMember' is not recognized as the name of a cmdlet", that indicates that the activedirectory module is either not loaded or
    not installed.
    If what is needed is a solution that does not rely on the activedirectory module here's code that relies on .Net 3.5. Like the Get-ADGroupMember with the -Recursive switch, this approach too suffers from the inability to find users that are primary groupmembers along the chain:
    $GroupName = Read-Host -Prompt 'GroupName'
    Add-Type -AssemblyName System.DirectoryServices.AccountManagement
    $domain = [System.DirectoryServices.AccountManagement.ContextType]::Domain
    $users = @([System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($domain,$GroupName)).GetMembers($true)

    • I consider changing primary group as bad practice.

      Its good only for external users to disallow access/logins to org. common places.
      And thus, It is not need in real file server environments

      ... but for your pleasure there is another onliner 😉

      Get-ADGroup 'REQUIRED_GROUP_NAME' | %{ Get-ADUser -LDAPFilter "(|(PrimaryGroupID=$($_.Sid-replace'.*-'))(memberOf:1.2.840.113556.1.4.1941:=$($_.DistinguishedName)))" }

  4. $group = Read-Host 'Group Name'
    (([adsisearcher]"memberOf:1.2.840.113556.1.4.1941:=$(([adsisearcher]"name=$group").Findone().Path.Substring(7))").findall() `
    | ? {$_.Properties.objectclass -contains "user"}).Properties.cn

    • True - it`s doesn`t in powershell 2.0. It does in 4.0 - tested in lab and production. I think it has to do with the way the objects are being returned it the pipe - but I`m not sure. So basically instead of directly calling the property "Properties", there should be another pipe like this:
      | ? {$_.Properties.objectclass -contains "user"} | % {write-host $_.Properties.cn}.

      I don`t know how to paste my code in that shiny format as the guys below. Can someone help me to understand how to use Github to post my code here?

    • We have two environment each with both version 4.0 and 5.0 and this code does not give any result.

      I suppose you can surround your code with the "code" brackets as described beneath the Comment field.
      get-process

  5. I came up with this little script to get the members of the group and the nested groups and create a report for the manager. Only thing that needs to be added is exporting the $report variable to a csv file which is simple.
    $groupName = Read-Host -Prompt "Enter Group Name"
    $report = @()

    $groupMembers = Get-ADGroupMember -Identity $groupName
    function GroupCheck($groupMembers, $groupName){
    write-output "Running GroupCheck on $groupName"
    #write-output $groupName
    #$groupName = ""
    foreach($member in $groupMembers){
    if($member.ObjectClass -eq "group"){
    write-output "Found $($member.name) in $groupName"
    $groupName = $member.Name
    #write-output $groupName
    Get-GrpMembers $member $groupName
    }
    }
    }
    function Get-GrpMembers ($member, $groupName) {
    write-output "Running Get-GrpMembers on $groupName"
    #write-output $groupName
    $groupMembers = Get-ADGroupMember -Identity $member
    Report $groupMembers $groupName

    }
    function Report ($groupMembers, $groupName){
    write-output "Running Report on $groupName"
    #write-output $groupName
    foreach ($member in $groupMembers){
    $global:report += [pscustomobject]@{
    Group = $groupName
    member = $member.name
    }
    }
    GroupCheck $groupMembers $groupName
    }
    Report $groupMembers $groupName
    GroupCheck $groupMembers $groupName