0 votes

Hi,

the script is only working for groups likeimage.png

A group with multiple values like here is not found: image.png

How to change the script to find them as well? I want to find all groups where the extensionAttribute5 is used with the value Permanent.

by (2.1k points)
0

Hello,

Sorry for the confusion, but we are not sure what exactly the issue is and the difference in criteria. Please, describe the desired and the actual behavior you are facing in all the possible details with live examples.

0

Hi,

based on the script shared previuosly I am able to find rule-based groups where (in my example) I look for extensionName5 is Permanent (1st screenshot).

However, the script is not finding a group where the value of property is Permanent or Apprenticeship.

0

Hello,

Sorry for the confusion, but we are not sure what exactly you mean. The criteria in both your screenshots are dedicated to users, not groups.

image.png

0

Hi, sorry, let me start from scratch

I have this report image.png

Parameters image.png

Script

$propertyName = "%param-PropertyName%"
$propertyValue = "%param-PropertyValue%"

try
{
    $groupCriteria = New-AdmCriteria -Type "group" -Expression {membershipType -eq "rule-based"}
    $Context.DirectorySearcher.AddCriteria($groupCriteria)

    $criteriaToCompare = New-AdmCriteria -Expression {$propertyName -eq $propertyValue}    
    $criteriaJsonToCompare = $criteriaToCompare.Item("*").Items.ToJson($null)
    $searchResultIterator = $Context.DirectorySearcher.ExecuteSearch()
    while ($Context.MoveNext($searchResultIterator))
    {
        $searchResult = $searchResultIterator.Current
        $group = $Context.BindToObjectBySearchResult($searchResult)

        foreach ($rule in $group.MembershipRules) 
        {
            if ($rule.Type -ne "ADM_BUSINESSUNITMEMBERSHIPTYPE_QUERY")
            {
                continue
            }

            $criteria = $rule.GetCriteria()

            if ($criteria.ToJson($NULL) | Select-String -Pattern $criteriaJsonToCompare -SimpleMatch)
            {
                $Context.Items.Add($searchResult)
                break
            }
        }
    }
}
finally
{
    if ($searchResultIterator) 
    { 
        $searchResultIterator.Dispose()
    }
}

If I run the report, I get only one rule based group image.png

This group has the following rule image.png

But, we have also some other rule based groups like with this rule image.png

This group is not found by the report/script.

Is there any chance to find also groups with the rule set of the second one?

My goal is: Find all rule base groups where the condition contains

extensionAttribute5 is Permanent

However we need also to find groups with rule

extensionAttribute5 is Permanent OR X OR Y

1 Answer

0 votes
by (305k points)

Hello,

Thank you for clarifying. The only option is to create each criteria you need to check in the script and compare with that of a group. Same like you do it now for a single criteria.

0

Hi, that is bad ... as I dont know the filters of all the other groups, just that "extensionAttribute5 is Permanent" as minimum.

Is there no other way to find groups?

0

Hello,

Unfortunately, there is no other approach you might use.

0

In case anyone else needs to find groups, here is a PS Script to find them

The script creates for each rule-based group a folder containing the queries as JSON files. Then it looks for the specific attribute/search string.

Result: image.png

Script

<#
.SYNOPSIS
    Analyzes Adaxes rule-based groups and searches for specific attributes in rule definitions.
.DESCRIPTION
    Extracts rule-based group definitions from Adaxes, saves them as JSON files, 
    and searches for specific attributes (like extensionAttribute5) within the rules.
.PARAMETER BasePath
    The directory where the group rule files will be saved.
.PARAMETER SearchAttribute
    The attribute to search for in group rules (default: "extensionAttribute5").
.EXAMPLE
    .\Analyze-AdaxesGroupRules.ps1 -BasePath "C:\temp\adx-groups-rules" -SearchAttribute "department"
.NOTES
    Requires Softerra.Adaxes.Adsi assembly to be installed on the system.
#>
param (
    [Parameter(Mandatory=$false)]
    [string]$BasePath = "C:\temp\adx-groups-rules",

    [Parameter(Mandatory=$false)]
    [string]$SearchAttribute = "extensionAttribute5"
)

# Load Adaxes assembly
try {
    [Reflection.Assembly]::LoadWithPartialName("Softerra.Adaxes.Adsi") | Out-Null
    if (-not [Softerra.Adaxes.Adsi.AdmNamespace]) {
        throw "Adaxes assembly loaded but types not available"
    }
}
catch {
    Write-Host "ERROR: Failed to load Softerra.Adaxes.Adsi assembly. Is Adaxes installed?" -ForegroundColor Red
    Write-Host "Error details: $($_.Exception.Message)" -ForegroundColor Red
    exit 1
}

# Function to ensure a directory exists
function Ensure-DirectoryExists {
    param ([string]$path)

    if (-not (Test-Path -Path $path -PathType Container)) {
        try {
            New-Item -Path $path -ItemType Directory -Force | Out-Null
            Write-Log -Message "Created directory: $path"
            return $true
        } catch {
            Write-Log -Level "ERROR" -Message "Failed to create directory: $path. Error: $($_.Exception.Message)"
            return $false
        }
    }
    return $true
}

# Logging function with color coding
function Write-Log {
    param (
        [string]$Level = "INFO",
        [string]$Message
    )

    $entry = "$(Get-Date -Format "yyyy-MM-dd HH:mm:ss") [$Level] $Message"
    switch ($Level.ToUpper()) {
        { $_ -in "ERR","ERROR" }   { Write-Host $entry -ForegroundColor Red }
        { $_ -in "WARN","WARNING" } { Write-Host $entry -ForegroundColor Yellow }
        { $_ -in "SUCCESS" }        { Write-Host $entry -ForegroundColor Green }
        default                     { Write-Host $entry -ForegroundColor Gray }
    } 
}

# Extract common name from distinguished name
function Get-CommonName {
    param ([string]$dn)

    if ($dn -match "CN=([^,]+)") {
        return $matches[1]
    } else {
        return ($dn -replace '[\\/:*?"<>|]', '_')
    }
}

# Convert DN to friendly path
function Convert-DNtoPath {
    param ([string]$dn)

    if ([string]::IsNullOrEmpty($dn)) { return "" }

    try {
        $domainPart = ""
        $containerPart = ""

        foreach ($part in ($dn -split '(?<!\\),')) {
            if ($part -match '^DC=') {
                $domainPart += $part.Substring(3) + '.'
            } else {
                $containerPart = $part.Substring(3) + '\' + $containerPart
            }
        }

        return ($domainPart.Trim('.') + '\' + ($containerPart.TrimEnd('\') -replace '\\,',','))
    }
    catch {
        return $dn
    }
}

Write-Log -Level "INFO" -Message "Starting Adaxes Group Rule Analysis"
Write-Log -Level "INFO" -Message "Base path: $BasePath"
Write-Log -Level "INFO" -Message "Search attribute: $SearchAttribute"

# Ensure base directory exists
if (-not (Ensure-DirectoryExists $BasePath)) {
    Write-Log -Level "ERROR" -Message "Cannot create base directory. Exiting."
    exit 1
}

# Connect to the Adaxes service
try {
    $ns = New-Object("Softerra.Adaxes.Adsi.AdmNamespace")
    $service = $ns.GetServiceDirectly("localhost")
    Write-Log -Level "SUCCESS" -Message "Connected to Adaxes service"
}
catch {
    Write-Log -Level "ERROR" -Message "Failed to connect to Adaxes service: $($_.Exception.Message)"
    exit 1
}

# Get GUIDs of all rule-based groups
try {
    $queries = $service.GetServiceObject("ADM_SERVICEOBJECTID_RULEBASEDGROUPQUERIES")
    $ruleBasedGroupGuids = $queries.GetRuleBasedGroups()
    Write-Log -Level "INFO" -Message "Found $($ruleBasedGroupGuids.Count) rule-based groups"
}
catch {
    Write-Log -Level "ERROR" -Message "Failed to get rule-based groups: $($_.Exception.Message)"
    exit 1
}

# Process each rule-based group
$processedGroups = 0
$failedGroups = 0

foreach ($ruleBasedGroupGuid in $ruleBasedGroupGuids) {
    try {
        # Get distinguished name of the group
        $ruleBasedGroup = $service.OpenObject("Adaxes://<GUID=$ruleBasedGroupGuid>", $null, $null, 0)
        $ruleBasedGroupDN = $ruleBasedGroup.Get("distinguishedName")
        $groupCommonName = Get-CommonName $ruleBasedGroupDN
        $rules = $ruleBasedGroup.MembershipRules

        Write-Log -Message "Processing group: $groupCommonName ($ruleBasedGroupDN)"

        # Create folder with just the common name
        $rulePath = Join-Path -Path $BasePath -ChildPath $groupCommonName
        Ensure-DirectoryExists $rulePath | Out-Null

        # Create a text file with the group's distinguished name
        $ruleBasedGroupDN | Out-File -FilePath "$rulePath\dn_info.txt"

        $ruleIndex = 0
        foreach ($rule in $rules) {
            switch ($rule.Type) {
                "ADM_BUSINESSUNITMEMBERSHIPTYPE_QUERY" {
                    try {
                        $ruleJSON = $rule.GetCriteria().ToJson($NULL)
                        $ruleJSON | Out-File -FilePath "$rulePath\rule_$($ruleIndex)_queryresults.json"
                        Write-Log -Message "Rule $($ruleIndex) - Query Results - Exported"
                    } catch {
                        Write-Log -Level "ERROR" -Message "Failed to export query rule $ruleIndex. Error: $($_.Exception.Message)"
                    }
                }
                "ADM_BUSINESSUNITMEMBERSHIPTYPE_SPECIFIC" {
                    #Write-Log -Message "Rule $($ruleIndex) - Specific Objects"
                }
                "ADM_BUSINESSUNITMEMBERSHIPTYPE_GROUP" {
                    #Write-Log -Message "Rule $($ruleIndex) - Group Members"
                }
                "ADM_BUSINESSUNITMEMBERSHIPTYPE_CONTAINER" {
                    #Write-Log -Message "Rule $($ruleIndex) - Container Objects"
                }
                default {
                    Write-Log -Message "Rule $($ruleIndex) - Unsupported Type: $($rule.Type)"
                }
            }
            $ruleIndex++
        }
        $processedGroups++
        Write-Log -Level "SUCCESS" -Message "Completed processing $groupCommonName"
    } catch {
        $failedGroups++
        Write-Log -Level "ERROR" -Message "Error processing group GUID $ruleBasedGroupGuid. Error: $($_.Exception.Message)"
    }
}

Write-Log -Level "INFO" -Message "Processing complete. Successfully processed $processedGroups groups. Failed: $failedGroups."

# Now search for the specified attribute in the exported JSON files
Write-Log -Level "INFO" -Message "Searching for '$SearchAttribute' in rule definitions..."

# Create a hashtable to store folders and their DN information
$folderDnInfo = @{}

# First, find all dn_info.txt files and store their content by folder
Get-ChildItem -Path $BasePath -Recurse -Filter "dn_info.txt" | ForEach-Object {
    $folder = $_.DirectoryName
    try {
        $dnContent = Get-Content $_.FullName -Raw -ErrorAction Stop
        $folderDnInfo[$folder] = $dnContent.Trim()
    }
    catch {
        $folderDnInfo[$folder] = "Error reading DN"
    }
}

# Create an array to store results
$results = @()

# Search for JSON files with the specified attribute
Get-ChildItem -Path $BasePath -Recurse -Filter "*.json" | ForEach-Object {
    $filePath = $_.FullName
    $content = Get-Content $filePath -Raw

    # Count occurrences of the property
    $matches = [regex]::Matches($content, $SearchAttribute)
    $count = $matches.Count

    # Store results if property is found
    if ($count -gt 0) {
        $folder = $_.DirectoryName
        $folderName = Split-Path $folder -Leaf  # Get just the folder name without the path
        $groupDN = if ($folderDnInfo.ContainsKey($folder)) { $folderDnInfo[$folder] } else { "No dn_info.txt found" }

        # Add to results
        $results += [PSCustomObject]@{
            'File' = $_.Name
            'Group' = $folderName
            'Occurrences' = $count
            'DN' = $groupDN
            'Path' = Convert-DNtoPath -dn $groupDN
        }
    }
}

# Output results
if ($results.Count -gt 0) {
    Write-Log -Level "SUCCESS" -Message "Found $($results.Count) occurrences of '$SearchAttribute' in rule definitions"

    Write-Host "`n=== Groups Using $SearchAttribute (Detail View) ===" -ForegroundColor Cyan
    $results | Format-Table -AutoSize

    Write-Host "`n=== Groups Using $SearchAttribute (Summary) ===" -ForegroundColor Cyan
    $results | Group-Object Group | Sort-Object Count, Name | Format-Table Name, Count

    # Export results to CSV
    $csvPath = Join-Path -Path $BasePath -ChildPath "AttributeSearch_$SearchAttribute.csv"
    $results | Export-Csv -Path $csvPath -NoTypeInformation
    Write-Log -Level "SUCCESS" -Message "Results exported to $csvPath"
} else {
    Write-Log -Level "INFO" -Message "No occurrences of '$SearchAttribute' found in rule definitions."
}

Write-Log -Level "SUCCESS" -Message "Analysis complete!"

Related questions

0 votes
1 answer

Hello, we have a forest with two trees that hold one domain each. There is a default tree-root trust (transitive, two-way) between the top domains. Since both trees are in ... Sid S-1-5-10 were found". Any help would be appreciated, thank you! Regards HarryNew

asked Oct 8, 2020 by HarryNew (270 points)
0 votes
1 answer

If I have 2 Active Directory Security groups in my domain - Group A Group B Is it possible to create a report that shows only users who have membership in both groups? For ... Jane Doe is in Group A AND Group B she would be included in the resulting report.

asked May 11, 2020 by sirslimjim (480 points)
0 votes
1 answer

Hello, I'm trying to execute a custom command through a Powershell script, but I'm struggling to pass multiple values to an AD Object Picker parameter. ... , $NULL, $NULL, 0) $obj.ExecuteCustomCommand($command.CommandID, $commandArguments) Thanks in advance!

asked Nov 24, 2021 by KelseaIT (320 points)
0 votes
1 answer

Hello, I would like to use the 'Department Number' attribute during user creation, but on my form it allows the end user to add addition values? I'm using Property Patterns to ... the '+' symbol it lets me add multiple. Is there a way to remove this? Thanks

asked Oct 27, 2020 by bavery (250 points)
0 votes
1 answer

Hello, I've identified an important functionality change between the old and new portal versions that is significantly impacting our workflow efficiency. Previous ... improve efficiency and user experience. Thank you for considering this feedback.

asked Jun 26 by wintec01 (2.1k points)
3,737 questions
3,415 answers
8,635 comments
550,311 users