Scroll Top
Evotec Services sp. z o.o., ul. Drozdów 6, Mikołów, 43-190, Poland

Mastering Active Directory Hygiene: Automating SIDHistory Cleanup with CleanupMonster

CleanupMonster - SIDHistory Cleanup1

Security Identifier (SID) History is a useful mechanism in Active Directory (AD) migrations. It allows users and groups in a new domain to retain access to resources that still rely on permissions from the old domain. However, once migrations are completed, these historical SIDs can become clutter, posing both security and administrative challenges. While it’s best to remove unnecessary SID History as soon as you’re done migrating, many environments skip this step. Over time, decommissioned or broken trusts make cleanup more difficult, and domain objects can accrue so many old entries that you lose track of what is still required.

Why Clean Up SID History?

  1. Security – Old SID History entries can become an attack vector. If an external or stale SID is still lurking, it could theoretically be used to gain unauthorized access.
  2. Administrative Overhead – Carrying extensive historical SIDs creates noise in AD. Administrators might spend extra time digging through or troubleshooting old references.
  3. Compliance – Certain regulations or internal policies encourage or require minimal external references in production domains.

When left unattended for years, stale SID History can balloon into thousands of entries. At that point, a methodical, controlled cleanup is needed to avoid disruptions.

To address these challenges, CleanupMonster now offers a new function, Invoke-ADSIDHistoryCleanup, that provides a structured way to remove unwanted SID History:

  • Targeted or Bulk Removal – You can select specific domains, organizational units, or even specific domain SIDs to include or exclude. This fine-grained control ensures you only remove what you intend.
  • Safety Limits – Cleanup can be restricted to a certain number of objects (e.g., 2 per run) or a certain number of SID entries (e.g., 5 total) per execution. This way, if something goes wrong, the impact is contained.
  • Reporting and Logging – The function creates HTML and XML reports that let you review changes before, during, and after they happen. This includes which objects were modified, what SID values were removed, and potential errors.
  • WhatIf Mode – Testing your plan is a must. By using -WhatIf, you can simulate the removal process to see exactly what would be deleted without making any changes.

By running the process in small increments, you can monitor how removing SID History affects user access. If issues arise, you can restore or adjust your approach before continuing.

Reporting Key to Success

Cleaning up SID History is pretty easy, however the key to doing it properly is to have a proper reporting and logging process that can help find out the root cause of user issues that surely will show up once you start removing thousands of SID History records. That's why CleanupMonster provides built-in HTML reporting and an email body prepared for this long cleanup process. It follows a similar pattern to the cleanup of computers and provides:

  • Overall report about SID History in a forest including all records, tabs with specific domain SIDs, and more
  • Current Run Tab – contains last run information about which objects were affected by the cleanup.
  • Historical Data Tab – contains history of one or more runs over time
  • Logs – each step is logged to file, but for the usefulness of the report, it's also included in HTML for easy access

The above image shows an overview of the current SID history in the forest. It tries to summarize data based on internal SID History (forest moves), External SID History (trusts), and Unknown SID history (deleted trusts, etc). The image below shows the removal of the SID History, which shows the SID from which the object was removed and how the object looked before and after the removal happened. It contains information about the action, when it happened, and whether it was successful.

The history tab contains information about all earlier runs of SID History Cleanup and what happened. Keep in mind that the XML file must be present, as the history is kept internally. Otherwise, the history will only keep current data.

The last tab, as shown in the image, contains logs from the script run. While the script writes the log to the file, it also displays its content if the HTML report is used inside TheDashboard.

Finally, every time the script runs, it prepares an HTML body for an email to be sent outside the script scope. Once the script runs, you can use the module's HTML body or create your own with available data.

Now that you know what you're getting, it's time to see how complicated the setup for the above is. Right?

Running the script

The first step is to install the CleanupMonster module

Install-Module CleanupMonster -Force -Verbose

Then, it's a matter of running a single function, Invoke-ADSIDHistoryCleanup. Below is an example of how you can schedule a slow, controlled cleanup over time. Please notice the use of specific SIDHistoryDomains, RemoveLimits, and other settings.

# Prepare splat
$invokeADSIDHistoryCleanupSplat = @{
    Verbose                 = $true
    WhatIf                  = $true
    IncludeSIDHistoryDomain = @(
        # 'S-1-5-21-3661168273-3802070955-2987026695'
        'S-1-5-21-853615985-2870445339-3163598659'
    )
    RemoveLimitSID          = 1
    RemoveLimitObject       = 2
    SafetyADLimit           = 1
    ShowHTML                = $true
    Online                  = $true
    LogPath                 = "$PSScriptRoot\ProcessedSIDHistory.log"
    ReportPath              = "$PSScriptRoot\ProcessedSIDHistory.html"
    DataStorePath           = "$PSScriptRoot\ProcessedSIDHistory.xml"
}

# Run the script
$Output = Invoke-ADSIDHistoryCleanup @invokeADSIDHistoryCleanupSplat
$Output | Format-Table -AutoSize

# Lets send an email
$EmailBody = $Output.EmailBody

# Send email with Microsoft Graph and using Mailozaurr module
Connect-MgGraph -Scopes 'Mail.Send' -NoWelcome
Send-EmailMessage -To '[email protected]' -From '[email protected]' -MgGraphRequest -Subject "Automated SID Cleanup Report" -Body $EmailBody -Priority Low -Verbose

Once you run the script, an HTML report is generated, and you can also send an email with pre-prepared content. Remember that the function has multiple other parameters that help delete only what you want. Please DO NOT RUN the script without first testing it out on TEST ENVIRONMENT and understanding what happens and how it affects the environment! This function is DANGEROUS! Here's a help file for the function that cleans up SID History on a global level.

NAME
    Invoke-ADSIDHistoryCleanup

SYNOPSIS
    Cleans up SID history entries in Active Directory based on various filtering criteria.


SYNTAX
    Invoke-ADSIDHistoryCleanup [[-Forest] <String>] [[-IncludeDomains] <String[]>] [[-ExcludeDomains] <String[]>] [[-IncludeOrganizationalUnit]
    <String[]>] [[-ExcludeOrganizationalUnit] <String[]>] [[-IncludeSIDHistoryDomain] <String[]>] [[-ExcludeSIDHistoryDomain] <String[]>]
    [[-RemoveLimitSID] <Nullable`1>] [[-RemoveLimitObject] <Nullable`1>] [[-IncludeType] <String[]>] [[-ExcludeType] <String[]>] [[-ReportPath] <String>]
    [[-DataStorePath] <String>] [-ReportOnly] [[-LogPath] <String>] [[-LogMaximum] <Int32>] [-LogShowTime] [[-LogTimeFormat] <String>] [-Suppress]
    [-ShowHTML] [-Online] [-DisabledOnly] [[-SafetyADLimit] <Nullable`1>] [-DontWriteToEventLog] [-WhatIf] [-Confirm] [<CommonParameters>]


DESCRIPTION
    This function identifies and removes SID history entries from AD objects based on specified filters.
    It can target internal domains (same forest), external domains (trusted), or unknown domains.
    The function allows for detailed reporting before making any changes.


PARAMETERS
    -Forest <String>
        The name of the forest to process. If not specified, uses the current forest.

    -IncludeDomains <String[]>
        An array of domain names to include in the cleanup process.

    -ExcludeDomains <String[]>
        An array of domain names to exclude from the cleanup process.

    -IncludeOrganizationalUnit <String[]>
        An array of organizational units to include in the cleanup process.

    -ExcludeOrganizationalUnit <String[]>
        An array of organizational units to exclude from the cleanup process.

    -IncludeSIDHistoryDomain <String[]>
        An array of domain SIDs to include when cleaning up SID history.

    -ExcludeSIDHistoryDomain <String[]>
        An array of domain SIDs to exclude when cleaning up SID history.

    -RemoveLimitSID <Nullable`1>
        Limits the total number of SID history entries to remove.

    -RemoveLimitObject <Nullable`1>
        Limits the total number of objects to process for SID history removal. Defaults to 1 to prevent accidental mass deletions.

    -IncludeType <String[]>
        Specifies which types of SID history to include: 'Internal', 'External', or 'Unknown'.
        Defaults to all three types if not specified.

    -ExcludeType <String[]>
        Specifies which types of SID history to exclude: 'Internal', 'External', or 'Unknown'.

    -ReportPath <String>
        The path where the HTML report should be saved. Used with the -Report parameter.

    -DataStorePath <String>
        Path to the XML file used to store processed SID history entries.

    -ReportOnly [<SwitchParameter>]
        If specified, only generates a report without making any changes.

    -LogPath <String>
        The path to the log file to write.

    -LogMaximum <Int32>
        The maximum number of log files to keep.

    -LogShowTime [<SwitchParameter>]
        If specified, includes the time in the log entries.

    -LogTimeFormat <String>
        The format to use for the time in the log entries.

    -Suppress [<SwitchParameter>]
        Suppresses the output of the function and only returns the summary information.

    -ShowHTML [<SwitchParameter>]
        If specified, shows the HTML report in the default browser.

    -Online [<SwitchParameter>]
        If specified, uses online resources in HTML report (CSS/JS is loaded from CDN). Otherwise local resources are used (bigger HTML file).

    -DisabledOnly [<SwitchParameter>]
        Only processes objects that are disabled.

    -SafetyADLimit <Nullable`1>
        Stops processing if the number of objects with SID history in AD is less than the specified limit.

    -DontWriteToEventLog [<SwitchParameter>]

    -WhatIf [<SwitchParameter>]
        Shows what would happen if the function runs. The SID history entries aren't actually removed.

    -Confirm [<SwitchParameter>]

    <CommonParameters>
        This cmdlet supports the common parameters: Verbose, Debug,
        ErrorAction, ErrorVariable, WarningAction, WarningVariable,
        OutBuffer, PipelineVariable, and OutVariable. For more information, see
        about_CommonParameters (https:/go.microsoft.com/fwlink/?LinkID=113216).

    -------------------------- EXAMPLE 1 --------------------------

    PS C:\>Invoke-ADSIDHistoryCleanup -Forest "contoso.com" -IncludeType "External" -ReportOnly -ReportPath "C:\Temp\SIDHistoryReport.html" -WhatIf

    Generates a report of external SID history entries in the contoso.com forest without making any changes.




    -------------------------- EXAMPLE 2 --------------------------

    PS C:\>Invoke-ADSIDHistoryCleanup -IncludeDomains "domain1.local" -IncludeType "Internal" -RemoveLimitSID 2 -WhatIf

    Removes up to 2 internal SID history entries from objects in domain1.local.




    -------------------------- EXAMPLE 3 --------------------------

    PS C:\>Invoke-ADSIDHistoryCleanup -ExcludeSIDHistoryDomain "S-1-5-21-1234567890-1234567890-1234567890" -WhatIf -RemoveLimitObject 2

    Shows what SID history entries would be removed while excluding entries from the specified domain SID. Limits the number of objects to process to 2.




    -------------------------- EXAMPLE 4 --------------------------

    PS C:\># Prepare splat

    $invokeADSIDHistoryCleanupSplat = @{
        Verbose                 = $true
        WhatIf                  = $true
        IncludeSIDHistoryDomain = @(
            'S-1-5-21-3661168273-3802070955-2987026695'
            'S-1-5-21-853615985-2870445339-3163598659'
        )
        IncludeType             = 'External'
        RemoveLimitSID          = 1
        RemoveLimitObject       = 2

        SafetyADLimit           = 1
        ShowHTML                = $true
        Online                  = $true
        DisabledOnly            = $true
        #ReportOnly              = $true
        LogPath                 = "C:\Temp\ProcessedSIDHistory.log"
        ReportPath              = "$PSScriptRoot\ProcessedSIDHistory.html"
        DataStorePath           = "$PSScriptRoot\ProcessedSIDHistory.xml"
    }

    # Run the script
    $Output = Invoke-ADSIDHistoryCleanup @invokeADSIDHistoryCleanupSplat
    $Output | Format-Table -AutoSize

    # Lets send an email
    $EmailBody = $Output.EmailBody

    Connect-MgGraph -Scopes 'Mail.Send' -NoWelcome
    Send-EmailMessage -To '[email protected]' -From '[email protected]' -MgGraphRequest -Subject "Automated SID Cleanup Report" -Body
    $EmailBody -Priority Low -Verbose




REMARKS
    To see the examples, type: "get-help Invoke-ADSIDHistoryCleanup -examples".
    For more information, type: "get-help Invoke-ADSIDHistoryCleanup -detailed".
    For technical information, type: "get-help Invoke-ADSIDHistoryCleanup -full".

Many organizations prefer not to remove all SID History at once. With small, incremental runs, you can do the following:

  • Schedule the cleanup (e.g., daily, weekly)
  • Limit the number of objects or SIDs each time
  • Review the report and logs
  • Confirm that users can still access essential resources
  • Increase or decrease cleanup speed as confidence grows

This approach helps build trust in the process and reduces the risk of widespread access issues.

Warnings and Best Practices

  • Always test with WhatIf – Verify the objects and SID values targeted before any real deletions.
  • Use RemoveLimitObject and RemoveLimitSID to make sure a limited number of SIDs are removed/gets affected (default one object being processed)
  • Backup – Ensure you have AD backups or ways to revert changes if you discover certain SID History was still required.
  • Communication—Inform your help desk or relevant teams that SID History entries are being removed so they can quickly troubleshoot access issues.
  • Prefer Early Cleanup – If you’re in the middle of a domain migration, do not postpone SID History cleanup indefinitely. Doing it when trusts are still available is vastly easier than waiting years.

Conclusion

Cleaning up SID History is an important but often forgotten step. By leveraging CleanupMonster’s Invoke-ADSIDHistoryCleanup, you have a safer path to gradually remove stale SID entries, maintain a cleaner Active Directory, and strengthen security. Test thoroughly, proceed incrementally, and preserve your logs for audit and troubleshooting.

For more information on how SID History works, you can review Microsoft’s documentation on SID History. The new feature is part of the CleanupMonster module, which handles tasks like cleaning up unused computer accounts. Check out the existing blog posts on CleanupMonster for details about its other functionalities.

While CleanupMonster is a one-shot solution to SID History Removal, remember that you should know what you're doing before even starting this process. It's there for a reason, and removing it, as proposed by CleanupMonster, has its consequences. This solution is mainly built when it's almost impossible to tell what is what anymore, and cleaning it up correctly is simply impossible. Additionally, please do not run this module if you're only after the SID History report. While the module has a ReportOnly switch, running a dedicated reporting command is better. You can get the SID history report from the  ADEssentials PowerShell module.

# Install module or update if nessecary
Install-Module ADEssentials -Force -Verbose

# Run the report
$Object = Show-WinADSIDHistory -Online -PassThru
$Object

The commands above deliver very similar output to the one shown in the screenshots above without the risk of running the cleanup tool. It's the preferred method of establishing if you have a problem that this tool tries to solve!

Posty powiązane