1

I am fairly new to Powershell. I am attempting to re-write the names of GPO backup folders to use their friendly name rather than their GUID by referencing the name in each GPO backup's 'gpresult.xml' file that is created as part of the backup. However, I do not understand how I can reference the specific object (in this case, the folder name) that is being read into the ForEach-Object loop in order to read into the file beneath this folder.

function Backup_GPO {

    $stamp = Get-Date -UFormat "%m%d" 
    New-Item -ItemType "directory" -Name $stamp -Path \\dc-16\share\GPO_Backups -Force | out-null # create new folder to specify day backup is made
    Backup-GPO -All -Path ("\\dc-16\share\GPO_Backups\" + "$stamp\" )
    Get-ChildItem -Path \\dc-16\share\GPO_Backups\$stamp | ForEach-Object {
        # I want to reference the current folder here
        [xml]$file = Get-Content -Path (folder that is being referenced in for loop)\gpresult.xml 
        $name = $file.GPO.Name
    }

I'm coming from Python, where if I want to reference the object I'm currently iterating on, I can do so very simply -

for object in list:
    print(object)

How do you reference the currently in-use object in Powershell's ForEach-Object command?

1
  • 3
    Most commonly people use $_, but $psitem can be used as an alternative. It's always worth checking the PowerShell help as it's pretty comprehensive on the basics of the language: ForEach-Object Commented Jun 2, 2019 at 19:08

4 Answers 4

3

I'm coming from Python, where if I want to reference the object I'm currently iterating on, I can do so very simply -

for object in list: print(object)

The direct equivalent of that in PowerShell is the foreach statement (loop):

$list = 1..3  # create a 3-element array with elements 1, 2, 3
foreach ($object in $list) {
  $object  # expression output is *implicitly* output
}

Note that you cannot directly use a foreach statement in a PowerShell pipeline.

In a pipeline, you must use the ForEach-Object cmdlet instead, which - somewhat confusingly - can also be referred to as foreach, via an alias - it it is only the parsing mode that distinguishes between the statement and the cmdlet's alias.


You're using the ForEach-Object cmdlet in the pipeline, where different rules apply.

Script blocks ({ ... }) passed to pipeline-processing cmdlets such as ForEach-Object and Where-Object do not have an explicit iteration variable the way that the foreach statement provides.

Instead, by convention, such script blocks see the current pipeline input object as automatic variable $_ - or, more verbosely, as $PSItem.


While the foreach statement and the ForEach-Object cmdlet operate the same on a certain level of abstraction, there's a fundamental difference:

  • The foreach statement operates on collections collected up front in memory, in full.

  • The ForEach-Object cmdlet operates on streaming input, object by object, as each object is being received via the pipeline.

This difference amounts to the following trade-off:

  • Use the foreach statement for better performance, at the expense of memory usage.

  • Use the ForEach-Object cmdlet for constant memory use and possibly also for the syntactic elegance of a single pipeline, at the expense of performance - however, for very large input sets, this may be the only option (assuming you don't also collect a very large dataset in memory on output).

Sign up to request clarification or add additional context in comments.

Comments

2

Inside the ForEach-Object scriptblock, the current item being iterated over is copied to $_:

Get-ChildItem -Filter gpresult.xml |ForEach-Object {
    # `$_` is a FileInfo object, `$_.FullName` holds the absolute file system path
    [xml]$file = Get-Content -LiteralPath $_.FullName
}

If you want to specify a custom name, you can either specify a -PipelineVariable name:

Get-ChildItem -Filter gpresult.xml -PipelineVariable fileinfo |ForEach-Object {
    # `$fileinfo` is now a FileInfo object, `$fileinfo.FullName` holds the absolute file system path
    [xml]$file = Get-Content -LiteralPath $fileinfo.FullName
}

or use a foreach loop statement, much like for object in list in python:

foreach($object in Get-ChildItem -Filter gpresult.xml)
{
    [xml]$file = Get-Content -LiteralPath $object.FullName
}

Comments

0

Another way...

$dlist = Get-ChildItem -Path "\\dc-16\share\GPO_Backups\$stamp"
foreach ($dir in $dlist) {
    # I want to reference the current folder here
    [xml]$file = Get-Content -Path (Join-Path -Path $_.FullName -ChildPath 'gpresult.xml')
    $name = $file.GPO.Name
}

Comments

0

Here's my solution. It's annoying that $_ doesn't have the full path. $gpath is easier to work with than $_.fullname for joining the two strings together on the next line with get-content. I get a gpreport.xml file when I try backup-gpo. Apparently you can't use relative paths like .\gpo_backups\ with backup-gpo.

mkdir c:\users\js\gpo_backups\
get-gpo -all | where displayname -like '*mygpo*' | 
  backup-gpo -path c:\users\js\gpo_backups\

Get-ChildItem -Path .\GPO_Backups\ | ForEach-Object {
  $gpath = $_.fullname
  [xml]$file = Get-Content -Path "$gpath\gpreport.xml"
  $file.GPO.Name   
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.