Home > Enterprise >  Powershell improperly reading data in a CSV
Powershell improperly reading data in a CSV

Time:11-09

I currently am writing a Powershell script that remotely removes users from a local admin group on a list of servers. The CSV headers are Computer and Name. For each entry of user (name), matches the server (computer).

Ex.

Computer,Name
Server1,User1
Server1,User2
Server2,User1

Script:

$List = Import-CSV C:\temp\LocalAdmin.CSV

$user = $List.Name
$objGroup = $List.Computer

write-host "Removing user" $user "from server" $objGroup "local admin group:" -ForegroundColor Green

Invoke-Command -ComputerName $objGroup -ScriptBlock {Remove-LocalGroupMember -Group "Administrators" -Member $using:user }
   
write-host "Completed."

When the script runs, it runs through perfectly fine the first time through, but then it runs through the script line by line for how many ever lines there are causing it to attempt to remove the users multiple times. Can someone help me fix this logic? It is almost like the CSV is being read as an array vs a list. I appreciate the help!

CodePudding user response:

I would say:

$List = Import-CSV C:\temp\LocalAdmin.CSV

ForEach ($Item in $list)
{

$user = $Item.Name
$objGroup = $Item.Computer

write-host "Removing user $user from server $objGroup local admin group:" -ForegroundColor Green

Invoke-Command -ComputerName $objGroup -ScriptBlock {Remove-LocalGroupMember -Group "Administrators" -Member $user }
}
   
write-host "Completed."

Not tested, but this should be your starting point.

CodePudding user response:

As for what you tried:

Given that $List contains an array of objects, with each element containing an object representing a row of the CSV file, $List.Name and $List.Computer similarly return arrays of property (column) values, courtesy of PowerShell's member-access enumeration

Therefore, $using:user refers to the array of all usernames, across all servers.

While the -Member parameter of Remove-LocalGroupMember does accept arrays, there are two problems with your approach:

  • At least hypothetically you'll run the risk of deleting users you shouldn't from certain servers, or you'll run into users that don't exist on a given server (though you could ignore that with -ErrorAction Ignore).

  • Since a given server name can appear multiple times in the CSV, the targeted user(s) will have already been deleted, starting with the second call to that server - this is the problem you saw.


TheStingPilot's helpful answer provides an effective solution: loop over the objects representing the CSV rows one by one, and call Invoke-Command for each target server, with only the username at hand.

The downside of this approach - which may or may not matter, depending on how many computers you target - is that forgo the benefits of parallel execution that you get when you pass multiple computer names to Invoke-Command's -Computer parameter (by default, up to 32 computers at a time are targeted in parallel; you can modify that number with
-ThrottleLimit).

To avoid multiple calls to a given server while preserving the benefits of parallel execution:

  • Build a hashtable from the CSV input that maps server names to user names.

  • Pass that hashtable to a single Invoke-Command call, as you tried, and let each remote computer look up the relevant usernames in it and act on them.

# Read the CSV file and create a hashtable (map) that
# maps server (computer) names to usernames to remove.
$serverUserMap = [ordered] @{}
Import-CSV C:\temp\LocalAdmin.CSV | 
  ForEach-Object {
    [array] $serverUserMap[$_.Computer] = $serverUserMap[$_.Computer]   $_.Name
  }

# * $serverUserMap.Keys now contains all unique server names, 
#   which can be passed to -ComputerName
# * Inside the script block, accessing the hashtable with the local
#   computer name as the key returns only the relevant user(s).
Invoke-Command -ComputerName $serverUserMap.Keys -ScriptBlock {
  Remove-LocalGroupMember -Group "Administrators" -Member ($using:serverUserMap)[$env:COMPUTERNAME]
}
  • Related