I'm trying to get the following script to export into a csv all of the local admins members and domain groups on a group of servers. It works fine to export the local admins, but I noticed that it doesn't export domain groups (ie: I have a Domain Admins group that's in the local administrators group and it doesn't show in the csv).
This is the code I have, any help would be appreciated:
param(
[parameter(Position=0,ValueFromPipeline=$true)]
$ComputerName=[Net.Dns]::GetHostName(),
[System.Management.Automation.PSCredential] $Credential,
[UInt32] $BlockSize=50
)
begin {
$WMIEnumOpts = new-object System.Management.EnumerationOptions
$WMIEnumOpts.BlockSize = $BlockSize
function Get-LocalAdminGroupMember {
param(
[String] $computerName,
[System.Management.Automation.PSCredential] $credential
)
$params = #{
"Class" = "Win32_Group"
"ComputerName" = $computerName
"Filter" = "LocalAccount=TRUE and SID='S-1-5-32-544'"
}
if ( $credential ) {
if ( $computerName -eq [Net.Dns]::GetHostName() ) {
Write-Warning "The -Credential parameter is ignored for the current computer."
}
else {
$params.Add("Credential", $credential)
}
}
Get-WmiObject #params | ForEach-Object {
$groupName = $_.Name
$_.GetRelated("Win32_Account","Win32_GroupUser","","",
"PartComponent","GroupComponent",$false,$WMIEnumOpts) | Select-Object `
#{Name="ComputerName"; Expression={$_.__SERVER}},
#{Name="Name"; Expression={$groupName}},
#{Name="Member"; Expression={$_.Caption -replace "^$($_.__SERVER)\\", ""}},
#{Name="Type"; Expression={$_.__CLASS}}
}
}
}
process {
$Filename = PATH HERE
$OutFileName = "C:\temp\admins.csv"
Get-Content $Filename | Foreach-Object {Get-LocalAdminGroupMember -computerName $_ | Select-Object * | Export-csv -NoType $OutFileName -Append}
Ah, the joys of trying to access network resources from a remote computer. You're going to lose anything that's a domain account doing what you're doing. It's jut how it works. The good news is that there's still a way to get the info you want, and you can even use Get-WmiObject to do it if you want. If you have not renamed the Administrators group (because really, who does that?), you can do this easily, but if you did and you have to look for the group by SID like you are above then you'll have to query the remote server like you are, and make adjustments with the query below with the modified name that you get back. Here's what I'd recommend doing, using the Win32_GroupUser class instead:
Get-WmiObject -ComputerName $Server -Query "SELECT * FROM win32_GroupUser WHERE GroupComponent = ""Win32_Group.Domain='$computerName',Name='Administrators'"""
To put it in place of what you have for your function, it could look something like this:
function Get-LocalAdminGroupMember {
param(
[String] $computerName,
[System.Management.Automation.PSCredential] $credential
)
$params = #{
"ComputerName" = $computerName
"Query" = "SELECT * FROM win32_GroupUser WHERE GroupComponent = ""Win32_Group.Domain='$computerName',Name='Administrators'"""
}
if ( $credential ) {
if ( $computerName -eq [Net.Dns]::GetHostName() ) {
Write-Warning "The -Credential parameter is ignored for the current computer."
}
else {
$params.Add("Credential", $credential)
}
}
Get-WmiObject #params |
Where{$_.PartComponent -match ':(.+?)\.Domain="(.+?)",Name="(.+?)"'}|
ForEach{
[PSCustomObject]#{
"ComputerName"=$computerName
"Name"='Administrators'
"Member"=$Matches[2..3] -join '\' -replace "^$computerName\\"
"Type"=$Matches[1]
}
}
}
Related
Working on a simple script to loop through a bunch of machines through a 3rd party system and output the machine, group, and the user to a PS object.
Have the script outputting the correct groups/users. However when a group has more than one user, then it renders it on the same line, instead of a new line. Just looking for insight on how to properly format the output so each result is on it's own line.
$params = $args
$Target = $args[0]
$PrivUser = "$($params[1])\$($params[2])"
$PrivPwd = ConvertTo-SecureString -String $params[3] -AsPlainText -Force
$cred = [pscredential]::new($PrivUser,$PrivPwd)
$Groups = #('Administrators','Power Users')
$results = #()
try {
Invoke-Command -ComputerName $Target -Credential $cred -HideComputerName -ScriptBlock {
$lgs = Get-LocalGroup -Name $Groups
Foreach ($lg in $lgs) {
$ms = Get-LocalGroupMember -Name $lg
#write-host $ms.Name
$output = New-Object PSObject -Property #{
Machine = $env:COMPUTERNAME
Group = "$lg"
Username=$ms
}
$results += $output
}
return $results
}
} catch {
throw "Unable to connect to target: $($args[0]) `n$_"
}
results:
Username Group Machine
-------- ----- -------
{BLT\clepley, BLT\clepley_admin, BLT\Domain Admins, BLT\svr.blt.div.ss...} Administrators BLT-SS-WEB
BLT\clepley_admin Power Users BLT-SS-WEB
Seems like you're missing an inner loop in case the membership is greater than one:
Invoke-Command -ComputerName $Target -Credential $cred -HideComputerName -ScriptBlock {
foreach($group in $using:Groups) {
foreach($member in Get-LocalGroupMember -Name $group) {
[pscustomobject]#{
Machine = $env:COMPUTERNAME
Group = $group
Member = $member.Name
ObjectClass = $member.ObjectClass
}
}
}
}
However note, there is no error handling here, hence, this assumes the Power Users Group exists in the remote computers.
It's also worth noting the use of the $using: scope modifier, which allows you to access the local variable $Groups in the remote scope.
I am creating a script that reads a list of computer names and collects data from security event logs about who is on the computer, how long they have been on for, and how long it has been since the computer has restarted. I have it working except that it does not output all the data into one CSV. I just receive one CSV file with one computer name.
function Get-KioskInfo {
param (
[parameter(ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True,Position=0)]
[Alias('PSComputerName','DNSHostName','CN','Hostname')]
[string]
$ComputerName = $env:COMPUTERNAME
)
#PARAM
$User = try {(Get-WmiObject -ComputerName $ComputerName Win32_ComputerSystem | Select-Object -ExpandProperty username).trimstart("NG\")} catch {Write-Output "User not detected";break}
$BootStart = ((get-date) - (Get-CimInstance win32_operatingsystem -ComputerName $ComputerName).LastBootUpTime).Days
#These variables are for the DATE & Time calculation
If ($user -NE $null)
{ Write-Verbose 1
# Do something
$Date1 = Get-date
Write-Verbose 2
$SP = Get-WinEvent -ComputerName $ComputerName -FilterHashTable #{LogName = "Security";ID="5379";Data=$User; StartTime=((Get-Date).AddDays(-1))}
Write-Verbose 3
$Date2 =($SP | select -first 1).timecreated
Write-Verbose 4
$USERLOGTIME = ($Date1-$Date2).hours.tostring("N2")
Write-Verbose 5
}
else{Write-Output "No user";break}
Write-Verbose 6
#Rename-Computer -ComputerName "Srv01" -NewName "Server001" -DomainCredential Domain01\Admin01 -Force ------ Rename script for computers if it is needed.
#$computers = Get-Content C:\Users\jaycbee\Desktop\kiosknames.txt ------ To load kiosk list
#foreach ($c in $computers) {start-job -Name $c -ScriptBlock ${Function:get-kioskinfo} -ArgumentList $c} for learning how to do a foreach script
Write "Computer Name: $Computername"
Write "---USER---"
Write "Name: $User"
Write "Log in Time $USERLOGTIME"
Write "Boot start $BootStart days ago"
$ComputerName | ForEach-Object {
if (Test-Connection -ComputerName $ComputerName -Count 1 -Quiet)
{
Invoke-Command -ComputerName $ComputerName {
}
} # Offline Check
else
{
Write-Host "Computer is Unreachable or Offline" -ForegroundColor Gray
}
} # Foreach
$Continue = Read-Host "WARNING! This will READ LIST of computers in \\ou\ouor-groups\Desktop_Support\SD\Kiosks\kiosknames.txt Type CONTINUE to proceed."
if ($Continue -eq "CONTINUE")
{
$Computers = Get-Content '\\ou\ouor-groups\Desktop Support\SD\Kiosks\kiosknames.txt'
foreach ($C in $Computers) {start-job -Name $c -ScriptBlock ${Function:get-kioskinfo} -ArgumentList $c
}
}
[pscustomobject]#{ Name = $ComputerName ; User = $User ; "User Log in time in hours" = $USERLOGTIME;"BootStart days ago" = $BootStart} | export-csv -path "\\ou\ouor-groups\Desktop Support\SD\Kiosks\test45$ComputerName.csv" -Append
} #Function
#For each-computer | do this at this location,
Continuing from my comment. I too wonder why the use of jobs for this use case. Unless you are doing this on hundreds of computers, thus needing parallel processing.
This refactor/formatting is just my way of making sense of what you posted. I'm old, and crowded code just really hurts my eyes. ;-} Yet, code the way you like of course. ;-}
I do not have an environment to test this, but give it a shot.
function Get-KioskInfo
{
param
(
[parameter(ValueFromPipeline = $True,ValueFromPipelineByPropertyName = $True,Position = 0)]
[Alias(
'PSComputerName',
'DNSHostName',
'CN',
'Hostname'
)]
[string]
$ComputerName = $env:COMPUTERNAME
)
($User = try
{
(Get-WmiObject -ComputerName $ComputerName Win32_ComputerSystem |
Select-Object -ExpandProperty username).trimstart("NG\")
}
catch
{
'User not detected'
break
}
)
($BootStart = ((get-date) - (Get-CimInstance win32_operatingsystem -ComputerName $ComputerName).LastBootUpTime).Days)
If ($user -NE $null)
{
($Date1 = Get-date)
($SP = Get-WinEvent -ComputerName $ComputerName -FilterHashTable #{
LogName = 'Security'
ID = '5379'
Data = $User
StartTime = ((Get-Date).AddDays(-1))
})
($Date2 = (
$SP |
select -first 1
).timecreated)
($USERLOGTIME = ($Date1-$Date2).hours.tostring('N2'))
}
else
{
'No user'
break
}
"Computer Name: $Computername
---USER---
Name: $User
Log in Time $USERLOGTIME
Boot start $BootStart days ago"
$ComputerName |
ForEach-Object {
if (Test-Connection -ComputerName $ComputerName -Count 1 -Quiet)
{Invoke-Command -ComputerName $ComputerName}
else
{Write-Warning -Message 'Computer is Unreachable or Offline'}
}
$UserMessage = '
WARNING!
This will READ LIST of computers in:
\\ou\ouor-groups\Desktop_Support\SD\Kiosks\kiosknames.txt
Type CONTINUE to proceed'
$Continue = Read-Host $UserMessage
if ($Continue -eq 'CONTINUE')
{
Get-Content '\\ou\ouor-groups\Desktop Support\SD\Kiosks\kiosknames.txt' |
foreach {
{start-job -Name $PSItem -ScriptBlock ${Function:get-kioskinfo} -ArgumentList $PSItem}
[pscustomobject]#{
Name = $ComputerName
User = $User
'User Log in time in hours' = $USERLOGTIME
'BootStart days ago' = $BootStart
}
} |
Export-Csv -path "$PWD\$ComputerName.csv" -Append
}
}
These didn't help me with my solution, but you were right about the start-jobs. I have to rework the entire script in order to get the correct info.
What I am trying to achieve here is add the servers and the updates that are not installed on the server to an array and create a new object that is going to display the names of the servers in one column and the missing updates on another column, but at the end I am getting an empty Grid-View table.
The values for the servers and updates are read from a file.
Write-Host
#Read the password from stdin and store it in a variable
$password = Read-Host -AsSecureString -Prompt "Enter your password"
Write-Host
#Get credentials and password for later user
$cred = New-Object System.Management.Automation.PSCredential ("Administrator#testing.local", $password )
#Get the list of available servers to test
$servers = Get-Content -Path $HOME\Desktop\servers.txt
#Get the list of available updates that need to be installed on the server
$available_updates = Get-Content $HOME\Desktop\update.txt
$add_updates = #()
$add_updates_and_servers = #()
#Get each server name from the list and execute the following commands
foreach ($server in $servers) {
#Test if the server is reponding
$ping = Test-Connection $server -Count 1 -Quiet
#If the above command returns True continue
if ($ping -eq "True") {
#Write a message saying Testing server_name
Write-Host "Testing $server"
foreach ($update in $available_updates) {
#Check if update is installed
$updates_from_os = Invoke-Command -ComputerName $server -Credential $cred -ScriptBlock { Get-HotFix | Select-Object -Property HotFixID | Where-Object -Property HotFixID -EQ $Using:update } -HideComputerName | Select-Object -ExpandProperty HotFixID
if (!$updates_from_os) {
$add_updates += $update
}
}
New-Object -TypeName PSObject -Property $updates -OutVariable final
$updates = #{
"Server" = $server
"Updates" = $add_updates
}
}
$add_updates_and_servers += $final
}
$add_updates_and_servers | Out-GridView
For what is probably happening with your script:
I suspect that each time you calling the statement New-Object -TypeName PSObject -Property $updates -OutVariable final You overwriting any previous created $final object which references to the same objects as your $add_updates_and_servers collection.
Anyways, try to avoid using the increase assignment operator (+=) to create a collection, instead stream the results to a variable (or even better, directly to next/final cmdlet: ... }| Out-GridView).
Something like:
$add_updates_and_servers = foreach ($server in $servers) {
$ping = Test-Connection $server -Count 1 -Quiet
if ($ping -eq "True") {
Write-Host "Testing $server"
$add_updates = #(
foreach ($update in $available_updates) {
$updates_from_os = Invoke-Command -ComputerName $server -Credential $cred -ScriptBlock { Get-HotFix | Select-Object -Property HotFixID | Where-Object -Property HotFixID -EQ $Using:update } -HideComputerName | Select-Object -ExpandProperty HotFixID
if (!$updates_from_os) { $update }
}
)
[PSCustomObject]#{
"Server" = $server
"Updates" = $add_updates
}
}
}
Note: in case you want each $update in a separate column, also have a look at: Not all properties displayed
I want to run a function in Powershell called Get-OSArchitecture which tells me whether a computer has a 32bit or 64bit system when you give it a domain name. However, it only accepts strings such as "SALES-DENNY" and not variables with stored strings such as $string1. I've played around with something called Out-String but this function is really stubborn with getting strings and nothing to do with variables.
The following code is for getting the global Get-OSArchitecture function:
function global:Get-OSArchitecture {
#Requires -Version 2.0
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$false,
Position=1,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true)]
[String[]]$ComputerName = $env:COMPUTERNAME
)#End Param
Begin
{
Write-Verbose "Retrieving Computer Info . . ."
}
Process
{
$ComputerName | foreach {
$ErrorActionPreference = 0
$Computer = $_
$Windir,$OSArchitecture,$OSVersion = Get-WmiObject -class Win32_OperatingSystem -ComputerName $_ |
foreach {$_.WindowsDirectory,$_.OSArchitecture,$_.Version}
$SysDrive = ($Windir -split ":")[0] + "$"
# $OSVersion[0]
# $OSArchitecture is only suppored on OSVersion -ge 6
# I was going to test for that, however now I just test if $OSArchitecture -eq $True
Write-Verbose "Operating System version on $Computer is: $OSVersion"
if ($OSArchitecture)
{
New-Object PSObject -Property #{
Hostname=$Computer
OSArchitecture=$OSArchitecture
SysDrive=$SysDrive
OSVersion=$OSVersion
WinDir=$WinDir
}
}
else
{
# check the program files directory
write-verbose "System Drive on $Computer is: $SysDrive"
$x64 = "\\$Computer\" + $SysDrive + "\Program Files (x86)"
if (test-path ("\\$Computer\" + $SysDrive))
{
if (test-path $x64)
{
New-Object PSObject -Property #{
Hostname=$Computer
OSArchitecture="64-bit"
SysDrive=$SysDrive
OSVersion=$OSVersion
WinDir=$WinDir
}
}
elseif (!(test-path $x64))
{
New-Object PSObject -Property #{
Hostname=$Computer
OSArchitecture="32-bit"
SysDrive=$SysDrive
OSVersion=$OSVersion
WinDir=$WinDir
}
}
}
else {"Something wrong determining the System Drive"}
}
} | select Hostname,OSArchitecture,SysDrive,WinDir,OSVersion
}#Process
End
{
}#End
}#Get-OSArchitecture
My problem begins below.
$string1 = "SALES-DENNY"
Get-OSArchitecture $string1
The above fails.
The below works.
Get-OSArchitecture "SALES-DENNY"
I expect the function to give out the correct architecture of the computer with the name "SALES-DENNY" but if I don't put it in as a string I always get a blank result.
Although it should not matter if you give the computername as hardcoded string or as a name or IP in a variable, I do believe you could improve the function by not testing the Program Files (x86) directory.
Instead, there are two other WMI functions you can rely on to get the 'bitness' of the OS:
function Get-OSArchitecture {
[CmdletBinding()]
param(
[Parameter(Mandatory=$false, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, Position=0)]
[string[]]$ComputerName = $env:COMPUTERNAME
)
process {
foreach ($computer in $ComputerName) {
Write-Verbose "Retrieving info for computer '$computer'"
$info = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $computer |
Select-Object #{Name = 'HostName'; Expression = { $_.PSComputerName}},
OSArchitecture,
#{Name = 'SysDrive'; Expression = { '{0}$' -f ($_.SystemDrive).Substring(0,1) }},
#{Name = 'WinDir'; Expression = { $_.WindowsDirectory}},
#{Name = 'OSVersion'; Expression = { $_.Version }}
if ($info.OSArchitecture) {
$info.OSArchitecture = '{0}-bit' -f ($info.OSArchitecture -replace '\D+','')
}
else {
$info.OSArchitecture = '{0}-bit' -f (Get-WmiObject -Class Win32_Processor -ComputerName $computer).AddressWidth
# or do:
# $info.OSArchitecture = '{0}-bit' -f (((Get-WmiObject -Class Win32_ComputerSystem -ComputerName $computer).SystemType -replace '\D+', '') -replace '86', '32')
}
# emit info
$info
}
}
}
Hope that helps
function get-localgroupmember {
[CmdletBinding()]
param(
[parameter(ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true)]
[string[]]$computername = $env:COMPUTERNAME
)
BEGIN {
Add-Type -AssemblyName System.DirectoryServices.AccountManagement
$ctype = [System.DirectoryServices.AccountManagement.ContextType]::Machine
}
PROCESS{
foreach ($computer in $computername) {
$context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList $ctype, $computer
$idtype = [System.DirectoryServices.AccountManagement.IdentityType]::SamAccountName
$group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($context, $idtype, 'Administrators')
$group.Members |
select #{N='Server'; E={$computer}}, #{N='Domain'; E={$_.Context.Name}}, samaccountName
} # end foreach
} # end PROCESS
}
"Win12R2", "W12SUS" | get-localgroupmember
What I want is the output to look like the following and I want to flag the users in the admin group that are NOT part of our standard setup. Really I want to ignore the SAM accounts that are the domain accounts but flagging them for now works. What is happening is there is a looping through the SAM accounts to create this output. However when the machine is offline I need to note that too.
I also do NOT want to use a ValueFromPipeline but rather get a list of PC names from this command $allComputers = Get-ADComputer -Filter 'PasswordLastSet -ge $date' -properties PasswordLastSet | select Name and then use that variable as the source to loop through.
This is my revised code but I'm having issues creating a custom object to add to an array when there seems to be looping in the $group.Members |select #{N='Server'; E={$computer}}, #{N='Domain'; E={$_.Context.Name}}, samaccountName
function get-localgroupmember {
[CmdletBinding()]
param(
[Parameter(Mandatory=$True,HelpMessage="Enter PC")]
[ValidateNotNullorEmpty()]
[object]$computername = $null
)
BEGIN {
$newArray = #();
Add-Type -AssemblyName System.DirectoryServices.AccountManagement
$ctype = [System.DirectoryServices.AccountManagement.ContextType]::Machine
}
PROCESS{
foreach ($computer in $computername) {
If (Test-Connection -ComputerName $computer.name -Quiet -Count 1) {
try {
$context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList $ctype, $computer.name
$idtype = [System.DirectoryServices.AccountManagement.IdentityType]::SamAccountName
$group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($context, $idtype, 'Administrators')
$group.Members | select #{N='Server'; E={$computer.name}}, #{N='Domain'; E={$_.Context.Name}}, samaccountName
$objComputer = [pscustomobject] #{
Server = $computer.name
Domain = $group.Members | select #{N='Domain'; E={$_.Context.Name}}
Account = $Computer.samaccountName
}
} catch {
$objComputer = [pscustomobject] #{
Server = $computer.name
Domain = "Error"
Account = "Error"
}
}
} else {
$objComputer = [pscustomobject] #{
Server = $computer.name
Domain = "Off-Line"
Account = "Off-Line"
}
} $arrayNew += $objComputer
} # end foreach
} # end PROCESS
return $arrayNew
}
$date = [DateTime]::Today.AddDays(-1)
$allComputers = Get-ADComputer -Filter 'PasswordLastSet -ge $date' -properties PasswordLastSet | select Name
get-localgroupmember -computername $allComputers | Out-GridView
To be honest I would not try to output an array object like you are. There is really no need for it. Just create each object as needed, and let it output directly (you really don't need to use return as the function will pass any output down the pipeline unless you specifically tell it otherwise, with something like Write-Host, or Out-File). Also, it looks like your input wants an object (that's pretty vague), but you are then trying to loop through that object, and use each record as the name of a PC, so what you really want for input is an array of strings. In that case change your type from [object] to [string[]]. Lastly, a good bit of your code can be simplified if you just expand the Name property when creating your $AllComputers variable. Oh, I lied, this is the last thing... Your return statement is not in a valid section of your function. It would need to be something like END{ Return $arrayNew }
Then you just have to add a list of excepted accounts to not flag, or add some logic in, or something. Honestly, your code should do pretty much everything you want it to do with a little syntax fixing. Here's based on your script, where it outputs all members of the group and flags any that arn't a local account with the name 'Administrator', and are not a domain account listed as OK (defined in the BEGIN section, currently "Domain Admins" or "Workstation Admin").
function get-localgroupmember {
[CmdletBinding()]
param(
[Parameter(Mandatory=$True,HelpMessage="Enter PC")]
[string[]]$computername
)
BEGIN {
Add-Type -AssemblyName System.DirectoryServices.AccountManagement
$ctype = [System.DirectoryServices.AccountManagement.ContextType]::Machine
$OKAccounts = ("Workstation Admin","Domain Admins" | ForEach{[regex]::Escape($_)}) -join "|"
}
PROCESS{
foreach ($computer in $computername) {
If (Test-Connection -ComputerName $computer -Quiet -Count 1) {
try {
$context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList $ctype, $computer
$idtype = [System.DirectoryServices.AccountManagement.IdentityType]::SamAccountName
$group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($context, $idtype, 'Administrators')
$group.Members | select #{N='Server'; E={$computer}}, #{N='Domain'; E={$_.Context.Name}}, samaccountName, #{N='Flag';E={If(!(($_.Context.Name -eq $Computer -and $_.samaccountname -match "Administrator") -or ($_.context.name -ne $Computer -and $_.samaccountname -match $OKAccounts))){"X"}}}
} catch {
[pscustomobject] #{
Server = $computer
Domain = "Error"
SamAccountName = "Error"
Flag = ''
}
}
} else {
[pscustomobject] #{
Server = $computer
Domain = "Off-Line"
SamAccountName = "Off-Line"
Flag = ''
}
}
} # end foreach
} # end PROCESS
}
$date = [DateTime]::Today.AddDays(-1)
$allComputers = Get-ADComputer -Filter 'PasswordLastSet -ge $date' -properties PasswordLastSet | select -Expand Name
#$allComputers = $env:COMPUTERNAME
get-localgroupmember -computername $allComputers | Out-GridView
That should give you output something like:
Server Domain SamAccountName Flag
------ ------ -------------- ----
TMTsLab TMTsLab Administrator
TMTsLab TMTsTacoTruck.com Domain Admins
TMTsLab TMTsTacoTruck.com SomeAcct1 X
TMTsLab TMTsTacoTruck.com SomeAcct2 X
TMTsLab TMTsTacoTruck.com TMTech X
Probably better yet would be to filter out the accounts you don't want, rather than just not flag them. So change the #{N='Flag';E={If(!(($_.Context.Name -eq $Computer -and $_.samaccountname -match "Administrator") -or ($_.context.name -ne $Computer -and $_.samaccountname -match $OKAccounts))){"X"}}} bit to a Where statement, so that line would be:
$group.Members | select #{N='Server'; E={$computer}}, #{N='Domain'; E={$_.Context.Name}}, samaccountName | Where { !(($_.Server -eq $_.Domain -and $_.samaccountname -match "Administrator") -or ($_.Server -ne $_.Domain -and $_.samaccountname -match $OKAccounts)) }
You'll also want to remove the Flag = '' lines from your Catch and Else scriptblocks as well. Which then the code only returns something like:
Server Domain SamAccountName
------ ------ --------------
TMTsLab TMTsTacoTruck.com SomeAcct1
TMTsLab TMTsTacoTruck.com SomeAcct2
TMTsLab TMTsTacoTruck.com TMTech
Full function code at that point:
function get-localgroupmember {
[CmdletBinding()]
param(
[Parameter(Mandatory=$True,HelpMessage="Enter PC")]
[string[]]$computername
)
BEGIN {
Add-Type -AssemblyName System.DirectoryServices.AccountManagement
$ctype = [System.DirectoryServices.AccountManagement.ContextType]::Machine
$OKAccounts = ("Workstation Admin","Domain Admins" | ForEach{[regex]::Escape($_)}) -join "|"
}
PROCESS{
foreach ($computer in $computername) {
If (Test-Connection -ComputerName $computer -Quiet -Count 1) {
try {
$context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList $ctype, $computer
$idtype = [System.DirectoryServices.AccountManagement.IdentityType]::SamAccountName
$group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($context, $idtype, 'Administrators')
$group.Members | select #{N='Server'; E={$computer}}, #{N='Domain'; E={$_.Context.Name}}, samaccountName | Where{ !(($_.Server -ieq $_.Domain -and $_.samaccountname -match "Administrator") -or ($_.Server -ne $_.Domain -and $_.samaccountname -match $OKAccounts)) }
} catch {
[pscustomobject] #{
Server = $computer
Domain = "Error"
Account = "Error"
}
}
} else {
[pscustomobject] #{
Server = $computer
Domain = "Off-Line"
Account = "Off-Line"
}
}
} # end foreach
} # end PROCESS
}