It’s generally a good practice to save the value of $_ to some other variable at the start of your ForEach-Object loops. That way if some other piece of code reassigns $_ later, you don’t lose the value you needed.
That said, if you already have your $ComputerName and $UserName arrays in memory, there’s really not much advantage to using ForEach-Object over foreach. The main difference is that you can pipe the results of ForEach-Object to another command, but there are ways to get around that even if you’re using foreach. Here’s one trick I use on occasion when I want to use foreach but still have it pipe results to something else:
# presumably, these are both arrays of strings that you want to enumerate over. $computerName = @() $userName = @() & { foreach ($computer in $computerName) { foreach ($user in $userName) { [pscustomobject] @{ ComputerName = $computer UserName = $user } } } } | Export-Csv -Path .\test.csv -NoClobber
By placing the foreach loops into an anonymous script block (curly braces) and invoking that script block with the call operator (&), the objects produced by the inner loop will be piped to Export-Csv one at a time, just as ForEach-Object allows you to do. Instead of an anonymous script block, you can also just put them into a function, like this:
function Some-FunctionName { [CmdletBinding()] param ( [string[]] $ComputerName, [string[]] $UserName ) foreach ($computer in $ComputerName) { foreach ($user in $UserName) { [pscustomobject] @{ ComputerName = $computer UserName = $user } } } } # presumably, these are both arrays of strings that you want to enumerate over. $computerName = @() $userName = @() Some-FunctionName -ComputerName $computerName -UserName $userName | Export-Csv -Path .\test.csv -NoClobber
The end result is the same.