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

Removing user from local administrator group based on data stored in Active Directory

CodeADSI

Now, and then I get small requests to build seemingly simple PowerShell scripts. This one I had to build had requirements as seen below:

  • Has to work on Windows 7 and Windows 10 (meaning PowerShell 2.0 support – Yikes!)
  • Has to remove a user from the Administrators group on multi-language systems (meaning you can't really use names)
  • Has to remove a user from the Administrators group only if the user is domain user
  • Has to remove a user from the Administrators group only if ExtensionAttribute10 in Active Directory has data in it

Even with PowerShell 5.1 simple task isn't that simple. We need to deal with group names through SIDs rather than names because each group name is different in different languages. The second problem is to distinguish whether a user is a local or domain user. Finally, I need to connect to Active Directory to verify if the user I am about to remove has ExtensionAttribute10 (or any other field in AD) filled in or not. This is a similar topic to my earlier post. Create a local user or administrator account in Windows using PowerShell. In last few weeks I have been exposed to PowerShell 2.0 and well, I don't like it! When you work with PowerShell 5.1 or higher, you're getting spoiled every single day.

While the stuff above is easy if you have Windows 10 with RSAT installed, it's a different game with PowerShell 2.0 and no tools installed to connect to AD. I've created functions below that can help with this small task

RemoveFromLocalGroup

function Get-InvariantGroup {
    [CmdletBinding()]
    param(
        [ValidateSet(
            'Access Control Assistance Operators',
            'Administrators'                     ,
            'Backup Operators'                   ,
            'Cryptographic Operators'            ,
            'Device Owners'                      ,
            'Distributed COM Users'              ,
            'Event Log Readers'                  ,
            'Guests'                             ,
            'Hyper-V Administrators'             ,
            'IIS_IUSRS'                          ,
            'Network Configuration Operators'    ,
            'Performance Log Users'              ,
            'Performance Monitor Users'          ,
            'Power Users'                        ,
            'Remote Desktop Users'               ,
            'Remote Management Users'            ,
            'Replicator'                         ,
            'System Managed Accounts Group'      ,
            'Users'
        )] $GroupName
    )
    $GroupSID = @{
        'Access Control Assistance Operators' = 'S-1-5-32-579'
        'Administrators'                      = 'S-1-5-32-544'
        'Backup Operators'                    = 'S-1-5-32-551'
        'Cryptographic Operators'             = 'S-1-5-32-569'
        'Device Owners'                       = 'S-1-5-32-583'
        'Distributed COM Users'               = 'S-1-5-32-562'
        'Event Log Readers'                   = 'S-1-5-32-573'
        'Guests'                              = 'S-1-5-32-546'
        'Hyper-V Administrators'              = 'S-1-5-32-578'
        'IIS_IUSRS'                           = 'S-1-5-32-568'
        'Network Configuration Operators'     = 'S-1-5-32-556'
        'Performance Log Users'               = 'S-1-5-32-559'
        'Performance Monitor Users'           = 'S-1-5-32-558'
        'Power Users'                         = 'S-1-5-32-547'
        'Remote Desktop Users'                = 'S-1-5-32-555'
        'Remote Management Users'             = 'S-1-5-32-580'
        'Replicator'                          = 'S-1-5-32-552'
        'System Managed Accounts Group'       = 'S-1-5-32-581'
        'Users'                               = 'S-1-5-32-545'
    }
    $GroupSID[$GroupName]
}
function Get-LocalUserFromDomain {
    [cmdletbinding()]
    param(
        [string] $NetBiosName,
        [Object] $Member
    )
    $Forest = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
    foreach ($Domain in $Forest.Domains) {

        #if ($Domain.Name -eq $DomainName) {
        $Root = $Domain.GetDirectoryEntry()
        $DomainDN = ($Root.distinguishedName)
        # Use the NameTranslate object.
        $objTrans = New-Object -comObject "NameTranslate"
        $objNT = $objTrans.GetType()

        # Invoke the Init method to Initialize NameTranslate by locating
        # the Global Catalog. Note the constant 3 is ADS_NAME_INITTYPE_GC.
        $objNT.InvokeMember("Init", "InvokeMethod", $Null, $objTrans, (3, $Null))

        # Use the Set method to specify the Distinguished Name of the current domain.
        # Note the constant 1 is ADS_NAME_TYPE_1779.
        $objNT.InvokeMember("Set", "InvokeMethod", $Null, $objTrans, (1, "$DomainDN"))

        # Use the Get method to retrieve the NetBIOS name of the current domain.
        # Note the constant 3 is ADS_NAME_TYPE_NT4.
        # The value retrieved includes a trailing backslash.
        $strDomain = $objNT.InvokeMember("Get", "InvokeMethod", $Null, $objTrans, 3)
        $strDomain = $strDomain -replace '\\'
        if ($strDomain -eq $NetBiosName) {
            break
        }
    }
    if ($domainDN) {
        #$domainDN = $domain.GetDirectoryEntry().distinguishedName

        $Searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$DomainDN")

        # Loop each user
        $Results = ForEach ($Name in $Member) {
            # $Searcher.filter = "(&(objectClass=user)(sAMAccountName= $SAMName))"
            $Searcher.filter = "(&(objectClass=user)(sAMAccountName=$($Name.SamAccountName)))"
            $User = $Searcher.FindOne()
            If ($User) {
                # Create PS Object
                $Object = New-Object PSObject -Property @{
                    Samaccountname      = (($User.Properties).samaccountname | Out-String)
                    Email               = (($User.Properties).mail | Out-String)
                    Mobile              = (($User.Properties).mobile | Out-String)
                    ExtensionAttribute10 = (($User.Properties).extensionattribute10 | Out-String)
                    LocalPath           = $Name.Path
                }
                $Object
            }
            $User
        }
        # Display results
        $Results
    }
}
function Remove-LocalUserFromGroup {
    [CmdletBinding()]
    param(
        [string] $Computer = $Env:ComputerName,
        [ValidateSet(
            'Access Control Assistance Operators',
            'Administrators'                     ,
            'Backup Operators'                   ,
            'Cryptographic Operators'            ,
            'Device Owners'                      ,
            'Distributed COM Users'              ,
            'Event Log Readers'                  ,
            'Guests'                             ,
            'Hyper-V Administrators'             ,
            'IIS_IUSRS'                          ,
            'Network Configuration Operators'    ,
            'Performance Log Users'              ,
            'Performance Monitor Users'          ,
            'Power Users'                        ,
            'Remote Desktop Users'               ,
            'Remote Management Users'            ,
            'Replicator'                         ,
            'System Managed Accounts Group'      ,
            'Users'
        )] $GroupName,
        [Array] $Users
    )
    foreach ($_ in $Users) {
        try {
            $User = $_.LocalPath
            $groupObj = [ADSI]"WinNT://$Computer/$GroupName,group"
            $groupObj.Remove($User)
        } catch {
            $ErrorMessage = $_.Exception.Message -replace [System.Environment]::NewLine
            if ($ErrorMessage -like '*An invalid directory pathname was passed*') {
                # This happens if you run the script more than once, and the object has already been removed from group but still in cache
            } elseif ($ErrorMessage -like '*Access is denied*') {
                Write-Warning "Remove-LocalUserFromGroup - Access denied. Are you running as administrator?"
            } else {
                Write-Warning "Remove-LocalUserFromGroup - $ErrorMessage"
            }
        }
    }
}
function Get-LocalUserFromGroup {
    [CmdletBinding()]
    param(
        [string] $Computer = $Env:ComputerName,
        [ValidateSet(
            'Access Control Assistance Operators',
            'Administrators'                     ,
            'Backup Operators'                   ,
            'Cryptographic Operators'            ,
            'Device Owners'                      ,
            'Distributed COM Users'              ,
            'Event Log Readers'                  ,
            'Guests'                             ,
            'Hyper-V Administrators'             ,
            'IIS_IUSRS'                          ,
            'Network Configuration Operators'    ,
            'Performance Log Users'              ,
            'Performance Monitor Users'          ,
            'Power Users'                        ,
            'Remote Desktop Users'               ,
            'Remote Management Users'            ,
            'Replicator'                         ,
            'System Managed Accounts Group'      ,
            'Users'
        )] $GroupName
    )
    # "Running Add-LocalUserToGroup" | Add-Content -Path 'C:\Temp\ScriptRunning.txt'
    $LocalUsers = Get-WmiObject Win32_UserAccount -Filter "LocalAccount=true"
    if ($LocalUsers) {
        $SID = Get-InvariantGroup -GroupName $GroupName
        $ObjSID = New-Object System.Security.Principal.SecurityIdentifier($SID)
        $GroupProvider = (($ObjSID.Translate([System.Security.Principal.NTAccount]) ).Value).Split("\")[1]
        try {
            #Write-Verbose " security principal: $User to the $GroupProvider group..."
            #"Add-LocalUserToGroup - Adding security principal: $User to the $GroupProvider group..." | Add-Content -Path 'C:\Temp\ScriptRunning.txt'
            $group = [ADSI]"WinNT://$Computer/$GroupProvider,group"
            $Users = $group.Invoke("Members") | ForEach-Object {
                #$_.GetType.Invoke().InvokeMember($null, 'GetProperty', $null, $_, $null)
                $LocalUser = ([ADSI]$_)
                $AdsPath = $LocalUser.InvokeGet('AdsPath').Replace('WinNT://', '')
                $IsGroup = ($LocalUser.SchemaClassName -like 'group')
                if (([regex]::Matches($AdsPath, '/')).count -eq 1) {
                    # DOMAIN\user
                    $MemberIsDomain = $True
                    $Name = $AdsPath.Replace('/', '\')
                } else {
                    # DOMAIN\machine\user
                    $MemberIsDomain = $False
                    $Name = $AdsPath.Substring($AdsPath.IndexOf('/') + 1).Replace('/', '\')
                }
                $NETBIOSName = $AdsPath.Split('/')[0]
                $SamAccountName = $Name.Split('\')[1]

                $Member = New-Object PSObject
                $Member | Add-Member Noteproperty 'GroupName' -Value  $GroupProvider
                $Member | Add-Member Noteproperty 'AccountName' -Value $Name
                $Member | Add-Member NoteProperty 'SamAccountName' -Value $SamAccountName
                $Member | Add-Member Noteproperty 'SID' -Value  ((New-Object System.Security.Principal.SecurityIdentifier($LocalUser.InvokeGet('ObjectSID'), 0)).Value)
                $Member | Add-Member Noteproperty 'IsGroup' -Value  $IsGroup
                $Member | Add-Member Noteproperty 'IsDomain' -Value  $MemberIsDomain
                $Member | Add-Member NoteProperty 'NetbiosName' -Value  $NETBIOSName
                $Member | Add-Member NoteProperty 'Path' -Value $LocalUser.Path
                $Member
            }
            foreach ($_ in $Users) {
                if ($_.IsDomain -eq $true -and $_.IsGroup -eq $false) {
                    Get-LocalUserFromDomain -NetBiosName $_.NetbiosName -Member $_
                }
            }


        } Catch {
            $ErrorMessage = $_.Exception.Message
            Write-Verbose $ErrorMessage
            #"Add-LocalUserToGroup - Adding $User to $GroupProvider failed: $ErrorMessage" | Add-Content -Path 'C:\Temp\ScriptRunning.txt'
        }
    } else {
        Write-Warning "Add-LocalUserToGroup - $User doesn't exists. Terminating."
        #"Add-LocalUserToGroup - $User doesn't exists. Terminating." | Add-Content -Path 'C:\Temp\ScriptRunning.txt'
    }
}

After you have those functions, you can do something like code below to remove user based on a non-empty ExtensionAttribute10.

# Code Execution
$DomainUsers = Get-LocalUserFromGroup -GroupName Administrators -Verbose
$UsersToRemove = foreach ($_ in $DomainUsers) {
    if (!$_.ExtensionAttribute10 -eq '') {
        $_
    }
}
Remove-LocalUserFromGroup -Users $UsersToRemove -GroupName Administrators

With small modifications, it's entirely possible to get other fields from AD and do your decisions based on that. With the help of ADSI, you don't need any other tools installed other than PowerShell 2.0. Hope this helps in case you ever get a similar task to do.

Related Posts