PSCustomObject foreach - powershell

I've created a custom object and I'm having some trouble with the output of one array.
$i = "computername"
$adsi = [ADSI]"WinNT://$i"
$Object = $adsi.Children | ? {$_.SchemaClassName -eq 'user'} | % {
New-Object -TypeName PSCustomObject -Property #{
ComputerName = $i.toupper() -join ''
UserName = $_.Name -join ''
Groups = ($_.Groups() |Foreach-Object {$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)}) -join ','
Disabled = Get-WmiObject -ComputerName $i -Class Win32_UserAccount -Filter "LocalAccount='$true'"|Select-Object -expandproperty Disabled
}
}
$object
The problem is with the Disabled array, instead of showing one item per line I'm getting
{False, True, False, False} I know I have to probably add at % somewhere on that line but I'm not sure where.
Anyone have any advice?

What you're seeing makes sense to me - you're creating an array of objects holding ComputerName, UserName, etc. and in Disabled you're getting an array of values because you're querying all local user accounts and getting their disabled status. I suspect what you want is to determine each user in turn is disabled. In which case, you need to extend the Filter on Get-WMIObject a bit to only get a single user.
$i = "computername"
$adsi = [ADSI]"WinNT://$i"
$Object = $adsi.Children | ? {$_.SchemaClassName -eq 'user'} | % {
$UserName = $_.Name -join '';
New-Object -TypeName PSCustomObject -Property #{
ComputerName = $i.toupper() -join ''
UserName = $UserName
Groups = ($_.Groups() |Foreach-Object {$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)}) -join ','
Disabled = Get-WmiObject -ComputerName $i -Class Win32_UserAccount -Filter "LocalAccount='$true' and name='$UserName'"|Select-Object -expandproperty Disabled
}
}
$object

Related

invoke command on all those servers to see which admin accounts are active on each server

I have been fetching both direct members and groups inside local administrators group in our remote machines. I want to get an output like below.
Also , if there are GROUP members inside local admin group then I want to organize only GROUP members like below.
MACHINE01,User01,TRUE,GROUP01;GROUP02
output:
"Computername","Members"
"MACHINE01","contoso\User01 contoso\User02 contoso\GROUP01 contoso\GROUP02
desired output:
Computername,Direct Members,Account Status,Group Members
MACHINE01,User01,TRUE,GROUP01;GROUP02
MACHINE01,User02,FALSE
MACHINE02,User05,TRUE,GROUP04;GROUP05;GROUP12
MACHINE02,User08,FALSE
MACHINE02,User12,FALSE
MACHINE44,User07,TRUE
script :
$server_list = #()
Import-Csv C:\temp\server3.csv | ForEach-Object {$server_list += $_.name}
invoke-command {
$members = net localgroup administrators |
where {$_ -AND $_ -notmatch "command completed successfully"} |
select -skip 4
New-Object PSObject -Property #{
Computername = $env:COMPUTERNAME
Members=$members
}
} -computer $server_list -HideComputerName | Select * -ExcludeProperty RunspaceID, PSComputerName, PSShowComputerName | Export-CSV c:\temp\local_admins2.csv -NoTypeInformation
Get-ADComputer -Filter * -SearchBase "OU=Servers,DC=test,DC=local" | select
name | Export-Csv C:\data\servers3.csv
Output:
"ComputerName","Direct Members","Account Status","Group Members"
"machine01","user01","OK",""
"machine01","user02","Degraded",""
"machine02","user03","OK",""
"machine02","user04","Degraded",""
LASTLY UPDATE OUTPUT WMI:
ComputerName Direct Members Account Status Group Members
------------ -------------- -------------- -------------
**MACHINE01 Administrator Degraded Domain Admins;IT-Admins**
MACHINE01 Theo OK
MACHINE01 LocalAdmin OK
**MACHINE02 Administrator Degraded DBA Admins;Software-Admins**
MACHINE02 Theo OK
MACHINE02 LocalAdmin OK
net localgroup does not output anything that differentiates between users or groups, so if possible, use
Get-LocalGroupMember and Get-LocalUser:
$server_list = (Import-Csv -Path 'C:\temp\server3.csv').name
$result = Invoke-Command -ComputerName $server_list -ScriptBlock {
$members = Get-LocalGroupMember -Group "Administrators"
$groups = $members | Where-Object {$_.ObjectClass -eq 'Group'} | ForEach-Object {($_.Name -split '\\')[-1]}
$users = #($members | Where-Object {$_.ObjectClass -eq 'User'})
if ($users.Count) {
# now loop over the user objects
$users | ForEach-Object {
$name = ($_.Name -split '\\')[-1]
$user = if ($_.PrincipalSource -eq 'Local') {
Get-LocalUser -Name $name -ErrorAction SilentlyContinue
}
else {
Get-ADUser -Filter "SamAccountName -eq '$name'" -ErrorAction SilentlyContinue
}
# output an object
[PsCustomObject]#{
'ComputerName' = $env:COMPUTERNAME
'Direct Members' = $name
'Account Status' = $user.Enabled
'Group Members' = $groups -join ';'
}
# clear the $goups here because you only want to list them once per server
$groups = $null
}
}
else {
# no users, just groups
[PsCustomObject]#{
'ComputerName' = $env:COMPUTERNAME
'Direct Members' = $null
'Account Status' = $null
'Group Members' = $groups -join ';'
}
}
} | Select-Object * -ExcludeProperty PSComputerName, RunspaceId
$result | Export-Csv -Path 'c:\temp\local_admins2.csv' -NoTypeInformation
If module Microsoft.Powershell.LocalAccounts is not available to you, you can experiment with module localaccount, but I have no experience with that..
I don't have that old OSes, so you'll have to test this.
Below uses WMI to query the servers for the group memberships:
$server_list = (Import-Csv -Path 'C:\temp\server3.csv').name
$result = foreach ($server in $server_list) {
$query = "Associators of {Win32_Group.Domain='$server',Name='Administrators'} where Role=GroupComponent"
$members = Get-WmiObject -Query $query -ComputerName $server |
Where-Object { $_.__CLASS -match '(User|Group)' } |
Select-Object Name, Caption,
#{Name = 'ObjectClass'; Expression = {$matches[1]}},
#{Name = 'ComputerName'; Expression = {$_.__SERVER}},
Status, LocalAccount, SID, Domain
$groups = #($members | Where-Object {$_.ObjectClass -eq 'Group'})
$users = #($members | Where-Object {$_.ObjectClass -eq 'User'})
if ($users.Count) {
# now loop over the user objects
$users | ForEach-Object {
# output an object
[PsCustomObject]#{
'ComputerName' = $_.ComputerName
'Direct Members' = $_.Name
'Account Status' = $_.Status
'Group Members' = ($groups.Name | Sort-Object -Unique) -join ';'
}
# clear the $groups here because you only want to list them once per server
$groups = $null
}
}
elseif ($groups.Count) {
# no users, just groups
[PsCustomObject]#{
'ComputerName' = $groups[0].ComputerName
'Direct Members' = $null
'Account Status' = $null
'Group Members' = ($groups.Name | Sort-Object -Unique) -join ';'
}
}
}
$result | Export-Csv -Path 'c:\temp\local_admins2.csv' -NoTypeInformation
Another alternbative is to use [ADSI]:
$server_list = (Import-Csv -Path 'C:\temp\server3.csv').name
$group = 'Administrators'
$members = foreach ($server in $server_list) {
try {
([ADSI]"WinNT://$server/$group,group").psbase.Invoke('Members') | ForEach-Object {
# test if local or domain
$ADSPath = $_.GetType().InvokeMember("ADSPath", 'GetProperty', $null, $_, $null)
$local = ($ADSPath -like 'WinNT://*')
# get the object name
$name = $_.GetType().InvokeMember('Name', 'GetProperty', $null, $_, $null)
# get object class
$class = $_.GetType().InvokeMember('Class', 'GetProperty', $null, $_, $null)
if ($class -eq 'User') {
if ($local) {
$flag = $_.GetType().InvokeMember("userflags", 'GetProperty', $null, $_, $null)
$enabled = !($flag -band 2) # ADS_UF_ACCOUNTDISABLE
}
else {
$enabled = (Get-ADUser -Filter "SamAccountName -eq '$name'" -ErrorAction SilentlyContinue).Enabled
}
}
else { $enabled = $null }
[PSCustomObject] #{
ComputerName = $server.ToUpper()
Group = $group
Name = $name
ObjectClass = $class
Enabled = $enabled
}
}
}
catch {
Write-Warning $_
}
}
$groups = #($members | Where-Object {$_.ObjectClass -eq 'Group'})
$users = #($members | Where-Object {$_.ObjectClass -eq 'User'})
$result = if ($users.Count) {
# now loop over the user objects
$users | ForEach-Object {
# output an object
[PsCustomObject]#{
'ComputerName' = $_.ComputerName
'Direct Members' = $_.Name
'Account Status' = $_.Enabled
'Group Members' = ($groups.Name | Sort-Object -Unique) -join ';'
}
# clear the $groups here because you only want to list them once per server
$groups = $null
}
}
elseif ($groups.Count) {
# no users, just groups
[PsCustomObject]#{
'ComputerName' = $_.ComputerName
'Direct Members' = $null
'Account Status' = $null
'Group Members' = ($groups.Name | Sort-Object -Unique) -join ';'
}
}
$result | Export-Csv -Path 'c:\temp\local_admins2.csv' -NoTypeInformation
Output from the above when testing
Result using Get-LocalGroupMember:
ComputerName Direct Members Account Status Group Members
------------ -------------- -------------- -------------
MACHINE01 Administrator False Domain Admins;IT-Admins
MACHINE01 LocalAdmin True
MACHINE01 Theo True
Result using WMI:
ComputerName Direct Members Account Status Group Members
------------ -------------- -------------- -------------
MACHINE01 Administrator Degraded Domain Admins;IT-Admins
MACHINE01 Theo OK
MACHINE01 LocalAdmin OK
Result using ADSI:
ComputerName Direct Members Account Status Group Members
------------ -------------- -------------- -------------
MACHINE01 Administrator False Domain Admins;IT-Admins
MACHINE01 Theo True
MACHINE01 LocalAdmin True

Fetching domain users from local groups with powershell

I want to migrate from one server to another one and because of that it is needed to add some local groups in the new server. In these local groups the users added belong to the domain.
Ex.:
Server | Members
---------------------------|------------------
Server\Group1 | Domain\User1, Domain\User2
Server\Group2 | Domain\User2, Domain\User3
The following link https://www.petri.com/use-powershell-to-find-local-groups-and-members seems to resolve this, but I am getting an unexpected result
This is the PowerShell script
# set variables
$server = $env:COMPUTERNAME
$localgroup = "Administrators"
$Group= [ADSI]"WinNT://$Server/$LocalGroup,group"
# get users name
$members = $Group.psbase.Invoke("Members")
$members | ForEach-Object
{
$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)
}
Get-WMIObject win32_group -filter "LocalAccount='True'" -computername $Server | Select PSComputername,Name,#{Name="Members";Expression={$_.GetRelated("Win32_UserAccount").Name -join ";"}}
The shown output is a two columns(although it should be 3, but PSComputerName is not being displayed where the Members column is empty)
Well, this is how I achieved the output and also exported it to a *.csv file
# set variables
$server = $env:COMPUTERNAME
$tableOutput = New-Object System.Collections.ArrayList
# get members
Function Get-Members($groupName){
$testgroup = [ADSI]"WinNT://$Server/$tmpGroupName,group"
$members = New-Object System.Collections.ArrayList
$testgroup.psbase.Invoke("Members") | ForEach-Object{
$searchFilter = $_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null) -replace "\."," "
$tmpUser = Get-ADUser -Filter {(Name -like $searchFilter) -or (SamAccountName -like $searchFilter)}
if($tmpUser){
[void]$members.Add($tmpUser.UserPrincipalName)
}
}
$members
}
Get-WMIObject win32_group -Filter { (LocalAccount='True') } -computername $Server | ForEach-Object{
$tmpGroup = $_
# get data
$tmpGroupName = $tmpGroup.Name
$members = Get-Members($tmpGroupName)
$tmpGroupDescription = $tmpGroup.Description
# save into object
$groupObject = New-Object -TypeName PSObject
$groupObject | Add-Member -MemberType NoteProperty -Name GroupName -Value $tmpGroupName
$groupObject | Add-Member -MemberType NoteProperty -Name GroupDescription -Value $tmpGroupDescription
$groupObject | Add-Member -MemberType NoteProperty -Name UsersList -Value $members
[void]$tableOutput.Add($groupObject)
}
$tableOutput | Select GroupName, GroupDescription, #{Name='Users';Expression={$_.UsersList -join ','}} | Export-CSV -Path 'C:\test\users.csv' -Delimiter ';' -NoTypeInformation
Any correction would be appreciated.

Ouput of script shows extra comma

I have a script where the script check NIC binding order.
$result = Invoke-Command -ComputerName $ComputerName -ScriptBlock {
function Get-BindOrder {
$Binding = (Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Linkage").Bind
$Return = New-Object PSobject
$BindingOrder = #()
foreach ($Bind in $Binding) {
$DeviceId = $Bind.Split("\")[2]
$Adapter = (Get-WmiObject Win32_Networkadapter | Where {$_.GUID -eq $DeviceId }).NetConnectionId
$BindingOrder += $Adapter
}
$BindingOrder
} #EndFunction
CLS
Get-BindOrder
}
$adapteresult = $result -join (",")
when I echo this $adapteresult = $result, I am getting an output as below
PS C:\> $adapteresult
vEthernet (10.211.14.0_20)
storage
Ethernet 5
Ethernet 4
Ethernet 2
Ethernet 6
The same variable when I added $adapteresult = $result -join (","), I am getting out put with extra command in between.
vEthernet (10.241.24.0_21),storage,Ethernet 5,,Ethernet 4,Ethernet 2,Ethernet 6,,
I do not want any extra comma in output. Expecting output like below:
vEthernet (10.241.24.0_21),storage,Ethernet 5,Ethernet 4,Ethernet 2,Ethernet 6,
(Get-WmiObject Win32_Networkadapter | Where {$_.GUID -eq $guid}).NetConnectionId appears to be returning $nulls that you are capturing in $BindingOrder. Displayed on the screen they take up no space but they are there regardless. Running a condensed version of your code locally on my machine ...
$results = ((Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Linkage").Bind | Foreach-Object {
$guid = $_.split("\")[2]
(Get-WmiObject Win32_Networkadapter | Where {$_.GUID -eq $guid}).NetConnectionId
})
Using that lets look at $results:
$results
$results.Count
$results -contains $null
Local Area Connection
6
True
On my machine I only have one match for Local Area Connection. However the $results have stored 6 items at least one of which is a $null. You need to filter out these results it seems in your query.
$Adapter = (Get-WmiObject Win32_Networkadapter | Where {$_.GUID -eq $DeviceId }).NetConnectionId
if($Adapter){
$BindingOrder += $Adapter
}
Should do it. if $adapter is null or empty string then it won't be added to $bindingorder
I have corrected myself and its working fine now
$a = (Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Linkage").Bind | ForEach-Object {
$guid = ($_ -split '\\')[2]
Get-WmiObject -Query "SELECT * FROM Win32_NetworkAdapter WHERE GUID='$guid'" |
Select-Object -ExpandProperty NetConnectionID
}
$b = $a -join","

How can I cast as a user object in PowerShell?

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.

Append CSV output coding issue

Hi i created the below script to audit local admin groups on remote hosts. It works fine, but since it only outputs the data after it has went through all the hosts, i am afraid the array will run out of buffer space before it has a chance to export to csv, so i have been trying to have it create and append the output from each host as it goes through the list except i cannot get the headers to display on the first line and append additonal lines below it. Below is the output i get when i try to append. The italicized words should be the headers and the other info should be listed in the next row. what am i doing wrong?
#{Server=pc1; Members=Administrator;DistinguishedName=DC=Domain,DC=com
This is how it should look. It looks this way if i dont append and i let it create the csv after it has finished going through the list of hosts
Server Members DistinguishedName
host1 Administrator;Admin2 DC=DOMAIN,DC=COM
$servers= get-content "C:\Scripts\AD Audits\Local Admin\workstations.txt"
$output = "c:\temp\local admin audit $CurrentDate.csv"
$results = #()
$disconnected = "Did not respond"
foreach($server in $servers)
{
$connected = Test-Connection $server -count 1 -quiet
if ($connected) {
"$server responded" | Out-File -append "c:\temp\LocalAdmin goodhosts $CurrentDate.txt"}
else {
"$server did not respond" | Out-File -append "c:\temp\LocalAdmin badhosts $CurrentDate.txt"}
$group =[ADSI]"WinNT://$server/Administrators"
$members = $group.Members() | foreach {$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null) }
$results += New-Object PsObject -Property #{
DistinguishedName = (get-adcomputer ($server) -properties * | select distinguishedname).distinguishedname
Server = $server
Members = $members -join ";"
}
$results | Export-Csv $Output -NoTypeInformation
}`
if($connected -eq $True) {
New-Object PSObject -Property #{
DistinguishedName = (Get-ADComputer $_).DistinguishedName
Server = $_
Members = $members -join ";"
}} else {write-host ""}
My suggestion is to use the pipeline rather than a foreach statement, so each object is written to the file as soon as it's processed.
$servers | ForEach-Object{
$connected = Test-Connection $_ -Count 1 -Quiet -ErrorAction SilentlyContinue
$state = if($connected) {"$_ responded"} else {"$_ did not respond"}
$state | Out-File -Append "c:\temp\LocalAdmin goodhosts $CurrentDate.txt"
$group =[ADSI]"WinNT://$_/Administrators,group"
$members = $group.Members() | ForEach-Object {$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null) }
if($connected)
{
New-Object PSObject -Property #{
DistinguishedName = (Get-ADComputer $_).DistinguishedName
Server = $_
Members = $members -join ";"
}
}
} | Export-Csv $Output -NoTypeInformation