Notice: Due to size constraints and loading performance considerations, scripts referenced in blog posts are not attached directly. To request access, please complete the following form: Script Request Form Note: A Google account is required to access the form.
Disclaimer: I do not accept responsibility for any issues arising from scripts being run without adequate understanding. It is the user's responsibility to review and assess any code before execution. More information

Monitoring Fine-Grained Password Policy Compliance (with Reports)

Password security is critical in enterprise environments, and Fine-Grained Password Policies (FGPP) provide granular control over password requirements for different user groups. However, monitoring FGPP compliance and detecting policy violations manually is time-consuming and error-prone.

I built a three-part automated solution to address these challenges:

  1. A PowerShell script that monitors FGPP policy changes and sends email alerts
  2. A script that compares compliance reports and alerts on new violations
  3. A web-based CSV viewer for analyzing compliance data

Why FGPP Monitoring Matters

FGPP allows different password requirements for different groups - executives might need 15-character passwords while service accounts have different rules. The challenge is ensuring these policies remain properly applied over time.

Common issues include:

  • Users moved out of policy groups
  • New policies created without proper assignments
  • Policy conflicts causing unexpected behavior
  • Accounts falling back to default domain policies

Script 1: FGPP Policy Change Monitor

The first script tracks changes to FGPP policies themselves, storing baseline data in JSON format and sending email alerts when changes are detected.

Smart Change Detection

The comparison logic prevents false alerts when group order changes by sorting before comparison:

# Compare AppliesTo groups (sort by DN to make order-insensitive)
$currentAppliesToSorted = $currentPolicy.AppliesTo | Sort-Object DN
$previousAppliesToSorted = $previousPolicy.AppliesTo | Sort-Object DN
$currentAppliesToJson = ($currentAppliesToSorted | ConvertTo-Json -Depth 10 -Compress)
$previousAppliesToJson = ($previousAppliesToSorted | ConvertTo-Json -Depth 10 -Compress)

if ($currentAppliesToJson -ne $previousAppliesToJson) {
    # Actual change detected - add to modifications list
    $modification = [PSCustomObject]@{
        PolicyName = $policyName
        ChangeType = "AppliesTo Modified"
        Previous = $previousPolicy.AppliesTo
        Current = $currentPolicy.AppliesTo
    }
    $changes.ModifiedPolicies += $modification
}
Script 2: Compliance Alert System

The second script compares CSV compliance reports to identify users who newly fall out of compliance.

Automatic File Detection

The script finds the two most recent compliance reports automatically:

function Get-RecentCSVFiles {
    param([string]$FolderPath, [string]$Pattern)
    
    $csvFiles = Get-ChildItem -Path $FolderPath -Filter $Pattern | 
                Sort-Object LastWriteTime -Descending
    
    if ($csvFiles.Count -lt 2) {
        Write-Log "Info: Need at least 2 CSV files to compare. Found: $($csvFiles.Count)"
        return $null
    }
    
    $newestFile = $csvFiles[0]  # File B (newest)
    $secondNewestFile = $csvFiles[1]  # File A (second newest)
    
    return @{
        FileA = $secondNewestFile
        FileB = $newestFile
    }
}

New Violation Detection

The script specifically identifies new non-compliance issues rather than all violations:

foreach ($record in $NewerData) {
    $identifier = $record.$IdentifierColumn
    
    # Skip records with missing or empty identifiers
    if (-not $identifier -or $identifier.Trim() -eq "" -or $identifier -eq "?") {
        Write-Log "Skipping record with missing/empty identifier: '$identifier'"
        continue
    }
    
    # Check if this is a non-compliant record
    $isNonCompliant = $record.ComplianceStatus -like "*non-compliant*"
    
    if ($isNonCompliant) {
        if (-not $olderDataHash.ContainsKey($identifier)) {
            # New user appearing as non-compliant
            $newNonCompliant += [PSCustomObject]@{
                Identifier = $identifier
                ChangeType = "New Non-Compliant User"
                Record = $record
            }
        }
    }
}

Email Alerts

The script generates clean HTML emails explaining the business impact:

$html += @"
<div class="summary">
    <h2>Security Compliance Issue Detected</h2>
    <p><strong>$($NewNonCompliantUsers.Count)</strong> users have been identified with new FGPP non-compliance issues.</p>
</div>

<div class="impact-section">
    <h3>Why This Matters</h3>
    <p>Fine-Grained Password Policy non-compliance represents a significant security risk:</p>
    <ul class="impact-list">
        <li>Accounts may not meet required password complexity standards</li>
        <li>Increased vulnerability to password-based attacks</li>
        <li>Potential regulatory compliance violations</li>
    </ul>
</div>
"@

The email alerts have two options, the first is the basic mode, that contain the information and changes to compliance:


Then there is the options for a more "detailed" information emails with the same data, the contains "why this matter" and "urgent action required":


Web-Based CSV Viewer

The third component is an HTML application for viewing and analyzing compliance CSV files, this can be used with the emails or without.

File Upload and Processing

The viewer supports drag-and-drop CSV upload which requires the CSV from the first script:

Papa.parse(file, {
    header: true,
    skipEmptyLines: true,
    complete: function(results) {
        csvData = results.data;
        filteredData = [...csvData];
        displayData();
        showDataSection();
    },
    error: function(error) {
        alert('Error parsing CSV: ' + error.message);
    }
});

Dynamic Table Generation

The viewer creates responsive tables with compliance-specific styling:

function displayData() {
    const headers = Object.keys(filteredData[0]);
    tableHeader.innerHTML = '<tr>' + 
        headers.map(header => `<th>${header}</th>`).join('') + 
        '</tr>';

    tableBody.innerHTML = filteredData.map(row => {
        return '<tr>' + headers.map(header => {
            let cellValue = row[header] || '';
            let cellClass = '';

            // Add styling for specific columns
            if (header === 'ComplianceStatus') {
                cellClass = cellValue.toLowerCase().includes('non-compliant') ? 
                    'status-non-compliant' : 'status-compliant';
            } else if (header === 'RiskLevel') {
                cellClass = `risk-${cellValue.toLowerCase()}`;
            }

            return `<td class="${cellClass}" title="${cellValue}">${cellValue}</td>`;
        }).join('') + '</tr>';
    }).join('');
}

Real-time Search

The application includes live search across all columns:

function filterData() {
    const searchTerm = searchBox.value.toLowerCase().trim();
    
    if (!searchTerm) {
        filteredData = [...csvData];
    } else {
        filteredData = csvData.filter(row => {
            return Object.values(row).some(value => 
                value && value.toString().toLowerCase().includes(searchTerm)
            );
        });
    }
    
    displayData();
}

This is show the website looks at the "drag and drop" screen:


When a CSV file is provided you get a list of people that are outside FGPP compliance which you can search:


File Structure

The solution uses this directory structure:

C:\Scripts\FGPP\
├── FGPP-Monitor.ps1
├── FGPP-Compliance-Monitor.ps1
├── compliance_monitor.log
├── Reports\
│   ├── FGPP_Compliance_Report_20250819_161813.csv
│   └── FGPP_Compliance_Report_20250820_161814.csv
└── fgpp-compliance.html

The combination of automated monitoring, intelligent comparison logic, and user-friendly data visualization creates a comprehensive FGPP compliance solution that reduces manual effort while improving security oversight.

Previous Post Next Post

نموذج الاتصال