Powershell Multipart WMI query output - powershell

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.

Related

PowerShell - server local admins reporting from a list of servers

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

Getting full member name from ComputerManagement using PowerShell?

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

Can't get members of other domains and PS version

I am learning to create new objects and combine properties from other objects. In this script I want to find out what the PS version is and also add some other properties like OS, IP etc... but I am running into 2 problems. We have 6 domains and I can't seem to iterate over each domain. I tried (Get-ADForest).Domains and can see the list of domains. It still only returns objects in the domain my workstation belongs to. The second issue is the Invoke-Command. The version always returns 5. I know many of the servers being returned do not have PSVersion 5.
function Get-PSVersion {
(Invoke-Command -Scriptblock {$PSVersionTable.PSVersion}) | Select Major
}
$servers = Get-ADComputer -Filter {(enabled -eq $true) -and (OperatingSystem -like "Windows Server* *")} -Properties * |
ForEach-Object {
$ps = Get-PSVersion
$server = $_
New-Object -TypeName PSObject -Property #{
Name = $server.Name
OS = $server.OperatingSystem
IPAddress = $server.IPv4Address
Location = $server.CanonicalName
PSVersion = $ps.Major
}
}
$servers | Select Name,Location,OS,IPAddress,PSVersion | FT -AutoSize
Ok so starting with the Invoke-Command, You need to tell that cmdlet which server to target, just calling it as you loop over server names will keep calling it on your local computer, so you'll need to use the -computername parameter, and provide your function an argument to pass to invoke-command. Which would look something like this:
function Get-PSVersion($name) {
(Invoke-Command -ComputerName $name -Scriptblock {$PSVersionTable.psversion | Select Major})
}
You'll notice I also moved your select, this isn't strictly necessary but imo it looks cleaner and means slightly less data gets sent over the network. note that this will create an object with a single property called Major, if you want just the version number returned as an integer you'd want to do it like this
function Get-PSVersion($name) {
(Invoke-Command -ComputerName $name -Scriptblock {$PSVersionTable.psversion.Major})
}
You'll need to add an extra loop into the script if you want to target more than one domain, basically you want an array of the domains you wish to target and then loop over that array calling get-adcomputer for each and specifying the domain name for the -server parameter. I've put a simplified example below you can incorporate into your own code.
$arr = #("test.domain","othertest.domain")
foreach($domain in $arr){
Get-ADComputer -Filter * -Server $domain
}
Hope that helps!
Got it to work. Thanks for the assistance.
clear-host
$arr = #("x.local","x.local")
foreach($domain in $arr){
$servers = (Get-ADComputer -Filter {(enabled -eq $true) -and (OperatingSystem -like "Windows Server* *")}-Server $domain -Properties *|Select -First 10)
}
$MasterList = #()
foreach ($server in $servers) {
$MyObj = New-Object PSObject -Property #{
Name = $server.Name
Os = $server.OperatingSystem
Ip = $server.IPv4Address
PSV = Invoke-Command -ComputerName $server.Name -ScriptBlock {$PSVersionTable.psversion}
}
$MasterList += $MyObj
}
$MasterList|Select Name,PSV,OS,IP

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.

Exporting results of foreach loop to CSV

The script reads in a .TXT file of computer names. It then checks to see if Windows Update is set to Auto or Manual; the computers name and update type are then exported to a CSV. At first, it was only exporting the final computer and it's type. I did some research and saw that creating an object to hold the name and type and then adding that object to an array would work. However, exporting the array to a CSV results in interesting data which is related to the array instead of the computer name and type. On top of that, I have the array printed out in the console pre-export and it looks to be the last computer and its type listed 9 times, the amount of computers on the one .TXT file I'm reading in. I know export-CSV now has an append parameter that could take care of this but we have much more PS 1.0 and 2.0 in the office and need it to work on those version.
clear-host
$item = new-object PSObject
$list = #()
$c=Get-Credential
$a = gc "c:\Scripts\pcList.txt"
ForEach ($b in $a)
{
$pcName = Get-WmiObject win32_service -filter 'name="wuauserv"' -computer $b -credential $c -ErrorAction SilentlyContinue |
select-object name,
#{N="System Name";E={$_.Systemname}}
$name = $pcName.'System Name'
$item | Add-Member NoteProperty Name $name -Force
$pcType = Get-WmiObject win32_service -filter 'name="wuauserv"' -computer $b - credential $c -ErrorAction SilentlyContinue |
select-object name, #{N="Startup Type";E={$_.StartMode}}
$type = $pcType.'Startup Type'
$item | Add-Member NoteProperty StartupType $type -Force
$list += $item
}
Write-Host $list
export-csv -Path "c:\Scripts\pcListCSV" -inputobject $list -notype
Ok, a couple things. The script looks fairly good really, all things considered, and a couple little tweaks and it should work fine. Let's approach this a tad different and just construct the object all at once instead of making it then adding noteproperties.
Also, we'll just make 1 call to GWMI (short for Get-WMIObject). I think this will work better for you. First off, making the GWMI call once means we have to select more than one thing when we make it. Since you are really making the exact same call twice, and selecting two different things from it each time, this just kind of makes sense. We're going to skip defining things in a select, and just let $pcName store all the data from the GWMI call. Though, first we're going to make sure that there's no old data in $pcName just to make sure we don't get duplicates.
clear-host
$item = new-object PSObject
$list = #()
$c=Get-Credential
$a = gc "c:\Scripts\pcList.txt"
ForEach ($b in $a)
{
if(Get-Variable pcName -ea SilentlyContinue){Remove-Variable pcName}
$pcName = Get-WmiObject win32_service -filter 'name="wuauserv"' -computer $b -credential $c -ErrorAction SilentlyContinue
Ok, now we create the object, and we'll just add it directly to the array. Here's where we define things like Name and StartupType as object properties, so when you display the array later it shows those two properties for each item. After that we'll close the ForEach loop and both display the list to the screen and pipe it to Export-CSV to save the results.
$list += New-Object PSObject -Property #{
Name = $pcName.SystemName
StartupType = $pcName.StartMode
}
}
$list
$list | Export-Csv "C:\Scripts\pcList.CSV" -notype
So, this doesn't check to make sure that the systems are online, or do any error checking like that, but if that's OK with you then I think this should do exactly what you need.