Private/Test-EFLocalGroupMembership.ps1

function Test-EFLocalGroupMembership {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$GroupName,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$MemberName,

        [ValidateRange(10, 60)]
        [int]$TimeoutSeconds = 15
    )

    $checkScript = {
        param($InputData)

        if ($null -eq (Get-Command -Name Get-LocalGroup -ErrorAction SilentlyContinue)) {
            return [pscustomobject]@{
                ProviderAvailable = $false
                GroupFound        = $null
                IsMember          = $null
            }
        }

        $groupName = [string]$InputData.GroupName
        $memberName = [string]$InputData.MemberName
        $groupUsesSid = $groupName -match '^S-1-(?:\d+-)+\d+$'
        $groupParameters = if ($groupUsesSid) {
            @{ SID = [Security.Principal.SecurityIdentifier]::new($groupName); ErrorAction = 'Stop' }
        }
        else {
            @{ Name = $groupName; ErrorAction = 'Stop' }
        }
        try {
            $group = Get-LocalGroup @groupParameters
        }
        catch {
            if ($_.FullyQualifiedErrorId -match '^GroupNotFound(?:,|$)' -or
                $_.Exception.GetType().FullName -eq 'Microsoft.PowerShell.Commands.GroupNotFoundException') {
                return [pscustomobject]@{
                    ProviderAvailable = $true
                    GroupFound        = $false
                    IsMember          = $false
                }
            }
            throw
        }
        $memberSid = if ($memberName -match '^S-1-(?:\d+-)+\d+$') {
            [Security.Principal.SecurityIdentifier]::new($memberName)
        }
        else {
            $computerName = [string]$InputData.ComputerName
            $accountName = if ($memberName -match '^\.\\(.+)$') {
                if ([string]::IsNullOrWhiteSpace($computerName)) { $Matches[1] } else { "$computerName\$($Matches[1])" }
            }
            elseif ($memberName -notmatch '[\\@]' -and -not [string]::IsNullOrWhiteSpace($computerName)) {
                "$computerName\$memberName"
            }
            else { $memberName }
            ([Security.Principal.NTAccount]::new($accountName)).Translate(
                [Security.Principal.SecurityIdentifier]
            )
        }

        $nativeSource = @'
using System;
using System.Runtime.InteropServices;
public static class EndpointForgeLocalGroupNative {
    [StructLayout(LayoutKind.Sequential)]
    public struct LOCALGROUP_MEMBERS_INFO_0 { public IntPtr Sid; }
 
    [DllImport("Netapi32.dll", CharSet = CharSet.Unicode)]
    public static extern int NetLocalGroupGetMembers(
        string serverName,
        string localGroupName,
        int level,
        out IntPtr buffer,
        int preferredMaximumLength,
        out int entriesRead,
        out int totalEntries,
        ref IntPtr resumeHandle);
 
    [DllImport("Netapi32.dll")]
    public static extern int NetApiBufferFree(IntPtr buffer);
}
'@

        $null = Add-Type -TypeDefinition $nativeSource -Language CSharp -ErrorAction Stop
        $resumeHandle = [IntPtr]::Zero
        $isMember = $false
        $membersRead = 0
        $maximumMembers = 4096
        $preferredMaximumLength = 65536
        do {
            $buffer = [IntPtr]::Zero
            $entriesRead = 0
            $totalEntries = 0
            $previousResumeHandle = $resumeHandle
            try {
                $status = [EndpointForgeLocalGroupNative]::NetLocalGroupGetMembers(
                    $null,
                    [string]$group.Name,
                    0,
                    [ref]$buffer,
                    $preferredMaximumLength,
                    [ref]$entriesRead,
                    [ref]$totalEntries,
                    [ref]$resumeHandle
                )
                if ($status -notin @(0, 234)) {
                    throw "Windows returned local-group status $status."
                }
                if ($totalEntries -gt $maximumMembers -or $membersRead + $entriesRead -gt $maximumMembers) {
                    throw "The requested local group exceeds the $maximumMembers-member safety limit."
                }
                if ($status -eq 234 -and
                    ($entriesRead -le 0 -or $resumeHandle -eq $previousResumeHandle)) {
                    throw 'Windows did not make progress while reading the requested local group.'
                }
                if ($entriesRead -gt 0 -and $buffer -eq [IntPtr]::Zero) {
                    throw 'Windows returned an incomplete local-group result.'
                }
                $entrySize = [Runtime.InteropServices.Marshal]::SizeOf(
                    [type][EndpointForgeLocalGroupNative+LOCALGROUP_MEMBERS_INFO_0]
                )
                for ($index = 0; $index -lt $entriesRead; $index++) {
                    $entryAddress = [IntPtr]::Add($buffer, $index * $entrySize)
                    $memberInfo = [Runtime.InteropServices.Marshal]::PtrToStructure(
                        $entryAddress,
                        [type][EndpointForgeLocalGroupNative+LOCALGROUP_MEMBERS_INFO_0]
                    )
                    $returnedSid = [Security.Principal.SecurityIdentifier]::new($memberInfo.Sid)
                    if ([string]::Equals($returnedSid.Value, $memberSid.Value, [StringComparison]::OrdinalIgnoreCase)) {
                        $isMember = $true
                        break
                    }
                }
                $membersRead += $entriesRead
            }
            finally {
                if ($buffer -ne [IntPtr]::Zero) {
                    $null = [EndpointForgeLocalGroupNative]::NetApiBufferFree($buffer)
                }
            }
            if ($isMember) { break }
        } while ($status -eq 234)

        [pscustomobject]@{
            ProviderAvailable = $true
            GroupFound        = $true
            IsMember          = $isMember
        }
    }

    Invoke-EFIsolatedCheck -ScriptBlock $checkScript -InputData @{
        GroupName    = $GroupName
        MemberName   = $MemberName
        ComputerName = $env:COMPUTERNAME
    } -TimeoutMilliseconds ($TimeoutSeconds * 1000) -StartupAllowanceMilliseconds 3000 `
        -Activity 'The local-group membership check'
}