foreach confusion

This topic contains 4 replies, has 3 voices, and was last updated by Profile photo of Barry Thomson Barry Thomson 3 years, 9 months ago.

  • Author
    Posts
  • #10372
    Profile photo of Barry Thomson
    Barry Thomson
    Participant

    Hi,

    Anyone able to help a nooby? I thought I had a handle on the foreach loop, but when I came to put it into practice, it proved I didn't...

    This is what I've got so far...any idea what I'm missing? Do I need to specify a comuter paramater after Get-WmiObject win32_networkadapter?

    Thank again

    $machines = Read-Host "Location and file name of your input"
    $filename = Read-Host "Enter a file name for the output"

    foreach ($machine in $machines) {

    Get-WmiObject win32_networkadapter -ErrorAction Ignore |

    where {$_.physicaladapter -eq $true} |

    Select-Object macaddress, netconnectionid, PSComputerName |

    Export-Csv -Path $env:USERPROFILE\$filename -NoTypeInformation

    }

  • #10374
    Profile photo of Don Jones
    Don Jones
    Keymaster

    Well...

    $machines is getting a single string. You're asking for a "location and file name of your input." The foreach loop will only have that one string to enumerate through, and it won't be a computer name. Was your intent to read computer names from a file?

    $inputfile = Read-Host "Enter input filename, must be one computer name per line"
    $computers = Get-Content $inputfile
    foreach ($computer in $computers) {
    }
    

    Second, yes, you do actually have to use the computer name with Get-WmiObject. The command defaults to querying the local computer.

    foreach ($computer in $computers) {
      Get-WmiObject -Class Win32_Whatever -ComputerName $computer
    }
    

    That's how you'd USE the $computer variable from the foreach loop.

    Additionally, you're not going to like the output when you send each computer through Export-CSV like that. You're going to end up overwriting the file, so it'll only contain the last computer processed. A typical approach would be to handle the processing and the output as separate steps.

    function Get-Stuff {
      $inputfile = Read-Host "Enter input filename, must be one computer name per line"
      $computers = Get-Content $inputfile
      foreach ($computer in $computers) {
        Get-WmiObject -Class Win32_Whatever -ComputerName $computer
      }
    }
    Get-Stuff | Export-CSV outputfile.csv
    

    Or even better, make the input a mandatory parameter so the shell will prompt for it:

    function Get-Stuff {
      Param([Parameter(Mandatory=$True)][string]$inputfile)
      $computers = Get-Content $inputfile
      foreach ($computer in $computers) {
        Get-WmiObject -Class Win32_Whatever -ComputerName $computer
      }
    }
    Get-Stuff | Export-CSV outputfile.csv
    

    Also, "Ignore" is not a valid ErrorAction. You probably mean "SilentlyContinue." Run "help about_common_param*" in the shell for more details. [EDIT: Was reminded Ignore is valid in v3; I've still got my head in v2 this morning]

  • #10376
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    Ignore is valid in PowerShell v3, actually. It behaves like SilentlyContinue, except it also doesn't add anything to the $error collection.

  • #10377
    Profile photo of Don Jones
    Don Jones
    Keymaster

    And to perhaps offer a more academic look at the ForEach loop itself: You need to start with a variable that contains one or more objects, such as strings.

    $machines = @('SERVER1','SERVER2','SERVER3')
    

    For example. Read-Host can actually only prompt for a single string, so it can't generate a collection of objects like that. Get-Content produces a collection, with each line in the file being a single object.

    ForEach ($machine in $machines) {
    }
    

    Takes everything in $machines (remember, it contains 1+ objects) and executes the loop one time "for each" of those objects. Each time through the loop, one object gets plucked out of $machines and stuffed into $machine. That way, inside the loop, you USE $machines to refer to just one thing at a time.

    ForEach ($machine in $machines) {
      Get-WmiObject Win32_BIOS
    }
    

    That'd query your local computer one time "for each" object in $machines. Why your local computer? Because that's what you told it to do. Being inside a ForEach loop doesn't magically make commands act differently. THEY don't know that $machines contains computer names. And not all commands can talk to remote computers.

    ForEach ($machine in $machines) {
      Get-WmiObject Win32_BIOS -Comp $machine
    }
    

    Assuming $machines contains computer names and not, say, AD users, that example would query WMI on each of the machines.

  • #10378
    Profile photo of Barry Thomson
    Barry Thomson
    Participant

    Don, thanks for very much. Enjoying your book too! 🙂

    Thank you

You must be logged in to reply to this topic.