I am using these commands to fetch the members of the Administrators group using PowerShell:
$computer = [ADSI]"WinNT://$s,computer"
$group = $computer.PSBase.Children.Find('Administrators', 'Group')
$Admin_Accounts = $group.PSBase.Invoke("members") | ForEach {
$_.GetType().InvokeMember("FullName", 'GetProperty', $null, $_, $null)
}
However, the results I am getting are just member names 'NAME' and not the full name displayed in the UI as 'ADomain/NAME'.
How can I achieve this?
Don't bother with ADSI. Use WMI instead:
$group = Get-WmiObject -Class Win32_Group -Filter "Name='Administrators'"
$group.GetRelated('Win32_UserAccount') | Select-Object -Expand Caption
Depending on your Powershell version you could use.
(Get-LocalGroupMember -Name Administrators).Name
This wil show all members of the local administrators group like this:
domain-or-computername\username
Tested on v5
Related
Beginner question. We only grant access to servers by AD group. We need to report who has admin access to a list of Windows servers. My auditor likes my Server Admins script however she also wants to know the group members first, last name. I don't need to use the ADGroupMember script, if there is a better way.
If someone could point me in the right direction that will be great. It's important I understand so I can do it myself next time : )
Thanks in advance
$computers = Get-content "c:\scripts\servers.txt"
ForEach ($Line In $computers)
{
#write-host $Line
Invoke-command -ComputerName $line -ScriptBlock { net localgroup administrators} | Get-ADGroupMember -Identity "$_????what goes here????" |%{get-aduser $_.SamAccountName | select userPrincipalName } | out-file "c:\scripts\'$line'LocalAdmin.txt"
}
This script works great but does not list out group members first, lastname
$computers = Get-content "c:\scripts\servers.txt"
ForEach ($Line In $computers)
{
#write-host $Line
Invoke-command -ComputerName $line -ScriptBlock { net localgroup administrators} | out-file "c:\scripts\'$line'LocalAdmin.txt"
}
If you really need information about the users in the local Administrators group, you can use the cmdlets from the PSv5.1+ Microsoft.PowerShell.LocalAccounts module.
However, note that local accounts just have a single .FullName property, not separate first and last name ones. Also, this property may or may not be filled in:
Invoke-Command -ComputerName (Get-Content c:\scripts\servers.txt) -ScriptBlock {
Get-LocalGroupMember -Group Administrators |
Where-Object ObjectClass -eq User |
Select-Object Sid |
Get-LocalUser
} |
Sort-Object PSComputerName |
Select-Object PSComputerName, Name, FullName
If domain users are among the group's members and you do need separate first and last name information, pipe to Get-ADUser instead of to Get-LocalUser - you can distinguish users by their source (where they are defined) via the .PrincipalSource property, available on the output objects from Get-LocalGroupMember from Window 10 / Windows Server 2016.
An alternative to mklement0's helpful answer, somewhat old school, using [adsi]:
$servers = Get-Content c:\scripts\servers.txt
Invoke-Command -ComputerName $servers -ScriptBlock {
$adsi = [adsi]"WinNT://$env:COMPUTERNAME,computer"
$adsi.PSBase.Children.Find('Administrators').PSBase.Invoke('members') |
ForEach-Object {
$Name = $_.GetType().InvokeMember('Name','GetProperty',$null,$_,$null)
$class = $_.GetType().InvokeMember('Class','GetProperty',$null,$_,$null)
$adspath = $_.GetType().InvokeMember('ADSPath','GetProperty',$null,$_,$null)
$sid = [System.Security.Principal.SecurityIdentifier]::new(
$_.GetType().InvokeMember('objectsid','GetProperty',$null,$_,$null),0
).Value
[pscustomobject]#{
Name = $Name
Class = $Class
Path = $adspath -replace '^WinNT://'
SecurityIdentifier = $sid
}
} | Sort-Object Class -Descending
} | Where-Object Class -EQ User
I am missing something fundamental in PowerShell.
I have a script that generates two collections, computer names with version details of a specific application and a separate user name list that is taken from the computer names list because the user names are in the computer names, for example a computer name is:
XXXXXX02jbloggs
The owner of this computer is jbloggs and jbloggs is a valid AD object which has a full name of joe blogs.
The ultimate objective of the script is to produce a report with computer names, owner SamAccountName, full name and application details, which the script will specifically check for.
For example,
what version(s) of Adobe Reader exist on this range of machines
So far I have:
$ErrorActionPreference = "SilentlyContinue"
$Computers = Get-ADComputer -Server BlahBlah.com -Filter {name -like "XXXXXX02*"} |
Select-Object -ExpandProperty Name
$Users = $Computers -Replace '\D*\d*(\w*)', '$1'
$Results = foreach ($Computer in $Computers) {
Get-CimInstance -ComputerName $Computer -ClassName Win32_Product |
Where-Object{$_.Name -like "*Adobe Reader*"} |
Select-Object PSComputerName, Name, Version, InstallDate
}
$FullNames = ForEach ($user in $Users) {
Get-ADUser -Server BlahBlah.com -Identity $User -Properties * |
Select-Object -ExpandProperty Name
}
$Results gets me a list of computer names, Adobe Reader xxx, the version and install date.
$FullNames gets me a list of the full names based on their user IDs
I do not know how to construct the script so it produces Full Name, User Name, Computer Name, Application Name and Install Date.
This is why I say I am missing something fundamental in PowerShell, I have been looking at custom objects, nested loops and other ideas but to no avail. Really looking for some advice on this type of problem as I several similar examples I need to accomplish.
Any advice would be greatly appreciated.
Get the single current user inside the foreach($computer in $Computers) instead of creating two separate foreach.
Add a calculated property to the select to include FullName in
$Result
$ErrorActionPreference = "SilentlyContinue"
$Computers = Get-ADComputer -Server BlahBlah.com -Filter {name -like "XXXXXX02*"} |
Select-Object -ExpandProperty Name
$Results = foreach ($Computer in $Computers) {
$User = $Computer -Replace '\D*\d*(\w*)', '$1'
$FullName = (Get-ADUser -Server BlahBlah.com -Identity $User -Properties *).Name
Get-CimInstance -ComputerName $Computer -ClassName Win32_Product |
Where-Object{$_.Name -like "*Adobe Reader*"} |
Select-Object PSComputerName, Name, Version, InstallDate,#{n='FullName';e=#{$FullName}}
}
When using this code:
$Prodservers = Get-ADComputer -Filter {OperatingSystem -like '*Server*'} -SearchScope Subtree -SearchBase $ProdSB -Server $DCprod -Credential $ProdCred -ErrorAction SilentlyContinue |
select -Expand DnsHostname
foreach ($P in $Prodservers) {
[PSCustomObject]#{
Hostname = $P
'Support team' = (Invoke-Command -ComputerName $P -ScriptBlock {$env:supportteam} -Credential $ProdCred)
'Local Admins' = (Invoke-Command -ComputerName $P -ScriptBlock {$ADSIComputer = [ADSI]('WinNT://localhost,computer');$lgroup = $ADSIComputer.psbase.children.find('Administrators', 'Group');$lgroup.psbase.invoke('members') | % {$_.GetType().InvokeMember('Name', 'GetProperty', $null, $_, $null)}} -Credential $ProdCred)
'Host Reachable' = [bool](Invoke-Command -ComputerName $P -ScriptBlock {1} -Credential $ProdCred)
}
}
This works, however an group membership of more than two members in the local administrators group returns similar to this:
{Administrator, Domain Admins, Prod Server Admin...
How would I expend the output to show the full membership?
Also after pointers for selecting only certain groups that match group name x or y or return True is group x is present etc.
You might be running into output display formatting issues, where the column data exceeds the displayable width in table format in PowerShell.
You can try use the Format-List cmdlet to display things in a list instead to see if your local administrators group with multiple members displays correctly. Check out the link above to see how it helps, but a basic example of using it would be:
Get-Service | Format-List
As for your filtering question, it looks like you're using reflection to invoke methods that collect that data, so it would be harder to use PS cmdlets to help there, so I would suggest getting that data as you do now, but do it separately, into a temporary variable, then filter the data there selecting your specific groups you want using something like this to match your group names, and in the if statement, put the relevant data into another variable, which you then use for your final output.
if ($item -match "groupNameX") { #Then... }
Finally worked it out.
Came across this answer.
First, found a script block that outputted the memberships as a PSObject property:
$SB = {
$members = net localgroup administrators |
where {$_ -AND $_ -notmatch "command completed successfully"} |
select -skip 4
New-Object PSObject -Property #{
Members=$members
}
}
Then modified the local admins column:
'Local Admins' = $admins.Members -join ','
The output is still truncated, however now instead of export-CSV showing the column contents as System.Object[] it now shows the full output with the separator specified in -join.
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.
I've got the following code which connects to every computer on the domain and checks the members of the local administrators group:
Foreach ($Computer in Get-ADComputer -Filter *){
$Path = $Computer.Path
$Name = ([ADSI]"$Path").Name
Write-Host $Name
$members = [ADSI]"WinNT://$Name/Administrators"
$members = #($members.psbase.Invoke("Members"))
ForEach($member in $members){
Write-Host $member.GetType().InvokeMember("Name", 'GetProperty', $null, $member, $null)
Write-Host $member.GetType().InvokeMember("AdsPath", 'GetProperty', $null, $member, $null)
}
}
I'm trying to store the value of $member in a $User object of some sort, so I can actually reference the attributes without all the crazy invoker stuff.
E.g., in pseudocode I want:
$user = (User) $member;
Write-Host $user.Name
Write-Host $user.AdsPath
I'm new to PowerShell, however... and I'm not sure if I really understand how to cast to an object type within it.
You want to create a custom object (or PSObject) with the specified members and values:
Foreach ($Computer in Get-ADComputer -Filter *){
$Path=$Computer.Path
$Name=([ADSI]"$Path").Name
write-host $Name
$members =[ADSI]"WinNT://$Name/Administrators"
$members = #($members.psbase.Invoke("Members"))
ForEach($member in $members){
$propsWanted = #('Name' , 'AdsPath') # An array of the properties you want
$properties = #{} # This is an empty hashtable, or associative array, to hold the values
foreach($prop in $propsWanted) {
$properties[$prop] = $member.GetType().InvokeMember($prop, 'GetProperty', $null, $member, $null)
}
$user = New-Object PSObject -Property $properties
$user # This is an object representing the user
}
}
Just to go over some of the changes:
I'm putting all of the property names you want into an array $propsWanted, and then iterating over that to invoke and get each one. This lets you easily work with more properties later, by adding the property names in a single place.
The $properties hashtable will store a key/value pair, where the key is the property name, and the value is the property value.
Once the hashtable is filled, you can pass it to the -Property parameter of New-Object when creating a PSObject.
You should use your new object and have a look at by testing a few things:
Pipe it to the Format- cmdlets to see various views:
$user | Format-Table
$user | Format-List
Check the values of its properties directly:
$user.Name
$user.AdsPath
If you make an array of these objects, you can filter them for example:
$user | Where-Object { $_.Name -like '*Admin' }
Alternatives
You might try using CIM or WMI, which will actually be a little friendlier:
CIM
$group = Get-CimInstance -ClassName Win32_Group -Filter "Name = 'Administrators'"
Get-CimAssociatedInstance -InputObject $group -ResultClassName Win32_UserAccount |
select -ExpandProperty Caption
Get-CimAssociatedInstance -InputObject $group -ResultClassName Win32_Group |
select -ExpandProperty Caption
WMI
$query = "ASSOCIATORS OF {Win32_Group.Domain='$($env:COMPUTERNAME)',Name='Administrators'} WHERE ResultClass = Win32_UserAccount"
Get-WmiObject -Query $query | Select -ExpandProperty Caption
$query = "ASSOCIATORS OF {Win32_Group.Domain='$($env:COMPUTERNAME)',Name='Administrators'} WHERE ResultClass = Win32_Group"
Get-WmiObject -Query $query | Select -ExpandProperty Caption
.NET 3.5 Required for this method:
Add-Type -AssemblyName System.DirectoryServices.AccountManagement
$ctype = [System.DirectoryServices.AccountManagement.ContextType]::Machine
$context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList $ctype, $env:COMPUTERNAME
$idtype = [System.DirectoryServices.AccountManagement.IdentityType]::SamAccountName
$group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($context, $idtype, 'Administrators')
$group.Members |
select #{N='Domain'; E={$_.Context.Name}}, samaccountName
Attribution
All of the above alternatives were taken directly from this "Hey, Scripting Guy!" article, The Admin's First Steps: Local Group Membership. It goes into detail about all of these, including the [ADSI] method. Worth a read.
How to Actually Cast
I just realized I didn't actually answer this question, even though it's not exactly what you needed. Classes/types are specified with square brackets. In fact, when you did this:
[ADSI]"WinNT://$Name/Administrators"
You casted a [String] (the string literal in ") to an [ADSI] object, which worked because [ADSI] knows what to do with it.
Other examples:
[int]"5"
[System.Net.IPAddress]'8.8.8.8'
Since we don't know the type of the "user" object you're seeking (or it's not even really loaded), you can't use this method.