Author Posts

January 30, 2014 at 6:32 am

I put together a script that searches for a user's profile on a list of computers and removes the profile if found. It works, but now I'm trying to clean it up and break it into a function or two, add help comments, logging, etc. Initially, I had a pair of nested ForEach loops:


ForEach ($c in $ComputerName) {. . .
ForEach ($u in $UserName) { . . . }
}

I am given understand that for the purposes of writing output to a log file, it would be better to pipe the variables to ForEach-Object. Is that correct? If so, how do I handle the objects inside the nested foreach loops? Referencing both the username and computername as $_ would be confusing at best, and in my case it wouldn't work as I need to reference the computer as well as the user from inside the 2nd Foreach.

January 30, 2014 at 6:50 am

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.

January 30, 2014 at 6:58 am

Ahh, so if the nested ForEach are inside a function, I would pipe the output to a log file when the function is called, rather than as a part of the function... that will work great, Thanks!