Portion of an A.D. cleanup script was working but is no longer

I have a script that cleans up A.D. and DNS objects after servers are decommissioned. One function in the overall script (below) would go through all GPOs in all of the domains and remove any orphaned SIDs.

   Function Remove-GPOUnknownSIDs {
      param ([parameter(mandatory = $true)][Microsoft.GroupPolicy.Gpo]$GPO)
      $name = $GPO.DisplayName
      $gpoSecurity = $GPO.GetSecurityInfo()
      $UnknownSIDs = $gpoSecurity.Trustee | Where {$_.SidType -Like "Unknown"}
      #$UnknownSIDs | Out-GridView -Wait

      foreach($UnknownSID in $UnknownSIDs) {
         $SIDToRemove = $UnknownSID.Sid.Value
         $gpoSecurity.RemoveTrustee($SIDToRemove)
         $GPO.SetSecurityInfo($gpoSecurity)
      }
   }

The thing that I do not understand is that this was working, and suddenly is throwing errors. In the larger script it is called like so:

   $aGPOs = Get-GPO -Domain "mydomain.com" -All
   #$aGPOs | Out-GridView -Wait -Title $aGPOs.Count

   foreach ($gpo in $aGPOs) {
      Remove-GPOUnknownSIDs -GPO $gpo
   }

As you can see in the code above, I tested to confirm that $aGPOs has members (130+ in the one domain), and inside the function I have tested that there are indeed GPOs that have unknown SIDs in them (at least a dozen). I have also confirmed the orphaned SID shows a blank line int $GPOSecurity. But when the code attempts to remove them I get the following error:

Exception calling "SetSecurityInfo" with "1" argument(s): "The request is not supported. (Exception from HRESULT: 0x80070032)" At line:19 char:13 + $GPO.SetSecurityInfo($gpoSecurity) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [], MethodInvocationException + FullyQualifiedErrorId : COMException

I am hoping I am missing something stupid simple here, but cannot wrap my head around why it has been working but suddenly is not.

Adding Verbose messages to step thru your functions comes in very handy to see what GPO and what SID it’s having issues with.

Function Remove-GPOUnknownSIDs {
    [CmdLetBinding()]
    param (
        [parameter(mandatory = $true)]
        [Microsoft.GroupPolicy.Gpo]$GPO
    )
    $name = $GPO.DisplayName
    Write-Verbose -Message ("{0} - Processing Group Policy Object {1}" -f $MyInvocation.MyCommand.Name, $name)
    $gpoSecurity = $GPO.GetSecurityInfo()
    $UnknownSIDs = $gpoSecurity.Trustee | Where {$_.SidType -Like "Unknown"}
    Write-Verbose -Message ("{0} - Found {1} unknown SIDs in Group Policy Object {2}" -f $MyInvocation.MyCommand.Name,$UnknownSIDs.Count, $name)


    foreach($UnknownSID in $UnknownSIDs) {
        $SIDToRemove = $UnknownSID.Sid.Value
        Write-Verbose -Message ("{0} - Processing unknown SID {1} in Group Policy Object {2}" -f $MyInvocation.MyCommand.Name,$SIDToRemove, $name)
        $gpoSecurity.RemoveTrustee($SIDToRemove)
        $GPO.SetSecurityInfo($gpoSecurity)
    }
}

Then you can run it silently until you have an issue and simply add the verbose switch to step through the logic.

...
Remove-GPOUnknownSIDs -GPO $gpo -Verbose
...

Thanks for the suggestion, I have been writing out to the console - I simply removed that for posting. That is what I do not understand, at every point, from the GPO name to the $gpoSecurity to the $UnknownSIDs is populated just how I would expect it to be.

Have you tried debugging in the ISE or VSCode? I think for something like this the ability to play with the actual object, and it’s various properties and methods might prove to be helpful. I like using the editors for scripting because they offer syntax guidance and other useful pieces of information.