简体   繁体   中英

Listing local administrator group membership on all domain computers

Complete powershell and scripting noob here - I don't even know enough to be dangerous. I need to query all PCs in the domain to determine what their local Administrators group membership is and send that output to a text/csv file.

I've tried numerous things like:

Import-Module -Name ActiveDirectory
Get-ADComputer -filter * |
Foreach-Object {
 invoke-command {net localgroup administrators} -EA silentlyContinue |
} |
Out-File c:\users\ftcadmin\test.txt

Which gets me an identical list repeated but seems to be hitting every domain PC. I'm guessing it's not actually running on the remote PCs though. Also tried:

$computers = Get-Content -Path c:\users\ftcadmin\computers.txt
invoke-command {net localgroup administrators} -cn $computers -EA silentlyContinue
Get-Process | Out-File c:\users\ftcadmin\test.txt

which is limited by predetermined list of PCs in the computers.txt file. A third thing I tried was this:

$a = Get-Content "C:\users\ftcadmin\computers.txt"
Start-Transcript -path C:\users\ftcadmin\output.txt -append
foreach ($i in $a)
  { $i + "`n" + "===="; net localgroup "Administrators"}
Stop-Transcript

which seems to have the most potential except the output is just

computername1
====
computername2
====

etc without any listing of the group members.

Any ideas from the community?

Copy and paste this function into your PowerShell console.

function Get-LocalGroupMember
{
    [CmdletBinding()]
    param
    (
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]$Name,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]$ComputerName = 'localhost',

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [pscredential]$Credential
    )
    process
    {
        try
        {
            $params = @{
                'ComputerName' = $ComputerName
            }
            if ($PSBoundParameters.ContainsKey('Credential'))
            {
                $params.Credential = $Credential
            }

            $sb = {
                $group = [ADSI]"WinNT://./$using:Name"
                @($group.Invoke("Members")) | foreach {
                    $_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)
                }
            }
            Invoke-Command @params -ScriptBlock $sb
        }
        catch
        {
            Write-Error $_.Exception.Message
        }
    }
}

Then, try this to use it:

Get-ADComputer -filter * | foreach {Get-LocalGroupMember -Name 'Administrators' -ComputerName $_.Name }

If you'd like to do some formatting you could get a list of computers and have all of the members beside each one.

Get-ADComputer -filter * | foreach {
    $members = Get-LocalGroupMember -Name 'Administrators' -ComputerName $_.Name
    [pscustomobject]@{
        'ComputerName' = $_.Name
        'Members' = $members    
    }   
}

This requires at least PowerShell v3. If you don't have that, I highly recommend upgrading to PowerShell v4 anyway.

In powershell version 5.1, the version that comes with WMF 5.1 or Windows 10 version 1607, there are now (finally) cmdlets for managing local users and groups. https://technet.microsoft.com/en-us/library/mt651681.aspx

For example, to get the members of the local Administrators group, you could use

Get-LocalGroupMember -Group "Administrators"

Unfortunately, these new cmdlets don't have a ComputerName parameter for running the command remotely. You'll need to use something like Invoke-Command to run the command remotely and the remote computer will need to have powershell version 5.1 as well.

The ComputerName parameter of Invoke-Command cmdlet doesn't accept pipeline input and it only accepts strings, so we first need to expand the name property returned by Get-ADComputer and store the strings in a variable. Thankfully the parameter accepts multiple strings, so we can simply use the $names variable in a single invoke-command call. Example below:

$names = Get-ADComputer -Filter * | Select-Object -ExpandProperty name
Invoke-Command -ScriptBlock {Get-LocalGroupMember -Group "Administrators"} -ComputerName $names

This is actually something that I have recently worked on a fair bit. It seems that there are two conventional ways to get members from the local Administrators group: WMI and ADSI.

In my opinion better method is to use a WMI query to get the members as this includes domain, so you know if the user/group listed is local to the server or is a domain account.

The simpler way using ADSI does not include this information, but is less likely to get Access Denied types of errors.

Towards this end I have cobbled together a script that checks AD for machines that have been active in the last 30 days (if it's not online, there's no need to waste time on it). Then for each of those it tries to do a WMI query to get the admin members, and if that fails it resorts to an ADSI query. The data is stored as a hashtable since that's a simple way to manage the nested arrays in my opinion.

$TMinus30 = [datetime]::Now.AddDays(-30).ToFileTime()
$Domains = 'ChinchillaFarm.COM','TacoTruck.Net'
$ServerHT = @{}
$Servers = ForEach($Domain in $Domains){Get-ADObject -LDAPFilter "(&(objectCategory=computer)(name=*))" -Server $Domain | ?{$_.lastLogonTimestamp -gt $TMinus30}}
$Servers.DNSHostName | %{$ServerHT.Add($_,$Null)}
ForEach($Server in ($Servers | ?{$(Test-Connection $_.DNSHostName -Count 1 -Quiet)})){
    Try{
        $GMembers = Get-WmiObject -ComputerName $Server -Query "SELECT * FROM win32_GroupUser WHERE GroupComponent = ""Win32_Group.Domain='$Server',Name='Administrators'"""
        $Members = $GMembers | ?{$_.PartComponent -match 'Domain="(.+?)",Name="(.+?)"'}|%{[PSCustomObject]@{'Domain'=$Matches[1];'Account'=$Matches[2]}}
    }
    Catch{
        $group = [ADSI]("WinNT://$Server/Administrators,group") 
        $GMembers = $group.psbase.invoke("Members")
        $Members = $GMembers | ForEach-Object {[PSCustomObject]@{'Domain'='';'Account'=$_.GetType().InvokeMember("Name",'GetProperty', $null, $_, $null)}}
    }

    $ServerHT.$Server = $Members
}

Then you just have to output to a file if desired. Here's what I would do that should output something like what you want:

$ServerHT.keys|%{"`n"+("="*$_.length);$_;("="*$_.length)+"`n";$ServerHT.$_|%{"{0}{1}" -f $(If($_.Domain){$_.Domain+"\"}), $_.Account}}

This would give you something like the following if the first two servers responded to WMI queries and the third did not:

===========
ServerSQL01
===========

ServerSQL01\SQLAdmin
TacoTruck\TMTech
TacoTruck\Stan2112

======
XWeb03
======

ChinchillaFarm\Stan2112

============
BrokenOld486
============

TMTech
Guest

That last one would trigger some red flags in my book if somebody put the Guest account in the local admin group, but I suppose that's probably one of the reason that you're doing this in the first place.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM