none
I want to count the elements in the pipeline and also process them RRS feed

  • Question

  • The following script works, but only because I save the pipeline contents in a variable.  I would like to not have to save it - see below. The commented section is my most recent failure. (And BTW I know the script has nothing to do with ZIP: that's the next step.

    function ZipUpModified {
        param ([string] $from, [string]$to, [datetime]$cutOff)
        try
        {
            Write-Host ("Copying {0} modified on or after {1} to {2} in execution at {3}" -f $from, $cutOff.toString(), $to, $(Get-Date))
            if (-not (Test-Path -Path $from)) {Throw "Source folder $from does not exist"}
            if (Test-Path -Path $to) {Throw "Target folder $to already exists"}
    # I would like to substitute the next 5 lines for the following 4; but I cannot seem to get the pipeline to reach the Copy_Item
    #       $fileCount=0
    #        Get-ChildItem $from -Recurse  |
    #            Where-Object {(New-TimeSpan -Start  $_.LastWriteTime -End $cutoff).TotalHours -le 0} |
    #            ForEach-Object {Copy-Item -Destination {Join-Path $to $_.FullName.Substring($from.length)}; $fileCount++; }
    #        Write-Host ("Copied {0} files/folders successfully at {1}" -f $fileCount, $(Get-Date))
            $filelist=Get-ChildItem $from -Recurse  |
                Where-Object {(New-TimeSpan -Start  $_.LastWriteTime -End $cutoff).TotalHours -le 0}
            $filelist | Copy-Item -Destination {Join-Path $to $_.FullName.Substring($from.length)}
            Write-Host ("Copied {0} files/folders successfully at {1}" -f $filelist.count, $(Get-Date))
        }
        catch
        {
            Write-Host $($_.Exception.ToString() -replace ".*: ")
            Write-Host "so we are stopping..."
        }
        finally
        {
            $IgnoreThis = Read-Host "hit OK or Enter"
        }
    }

    ### Main
    $cutOff= (Read-Host 'Enter date of most recent backup (mm/dd/yyyy):') | Get-Date
    ZipUpModified "C:\Users\Jonathan\Desktop\Test_BU\SRC" "C:\Users\Jonathan\Desktop\Test_BU\TGT" $cutOff
    ## two more calls to ZipUpModified

    Monday, March 31, 2014 5:06 PM

Answers

  • Whatever floats your boat.  You're still using the pipeline in my suggestion, but it's ForEach-Object that's receiving the pipeline input instead of Copy-Item.  You still get all the streaming advantages.  If it really bothers you that much to use a named parameter, you could have also done this:

    ForEach-Object {$_ | Copy-Item -Destination {Join-Path $to $_.FullName.Substring($from.length)}; $fileCount++}

    Monday, March 31, 2014 7:23 PM

All replies

  • Collect the objects first. Then you can count after collecting and then iterate:


    $items = get-childitem ...
    "There are {0} items in the collection." -f $items.Count
    foreach ( $item in $items ) {
    }
    

    If you insist on ForEach-Object, you can only count as you go and then output the count after your iteration is completed.


    $itemCount = 0
    get-childitem ... | foreach-object {
      $itemCount += 1
    }
    "There are {0} items in the collection." -f $itemCount
    

    Advantage of foreach (instead of ForEach-Object) is you can get a count before iterating, but iteration has to complete first. Advantage of ForEach-Object is that you can take advantage of the pipeline, but you can't get a count until after your iteration is done.


    -- Bill Stewart [Bill_Stewart]

    Monday, March 31, 2014 5:17 PM
    Moderator
  • The only problem I see with the lines you have commented out is that you've only specified a -Destination when calling Copy-Item.  That works if you give Copy-Item some pipeline input, but in this case, you're not doing that:

    ForEach-Object {Copy-Item -Destination {Join-Path $to $_.FullName.Substring($from.length)}; $fileCount++; }

    Adding -Path $_ to your call to Copy-Item inside the ForEach-Object loop should take care of that.
    Monday, March 31, 2014 6:00 PM
  • Guys - Thanks for the quick response

    Bill - I think you are recommending the approach I had in the submitted code.

    David - the whole point is that I want to use the pipeline, not the Path argument, and I had no luck with your suggestion, which I took as:

            Get-ChildItem $from -Recurse  |
                Where-Object {(New-TimeSpan -Start  $_.LastWriteTime -End $cutoff).TotalHours -le 0} |
                ForEach-Object {Copy-Item -Path $_ -Destination {Join-Path $to $_.FullName.Substring($from.length)}; $fileCount++}

    However, all this did make me go read more documentation, and there is a -PassThru argument to Copy-Item which creates pipeline output after the copy.  Specifically:

            $fileCount=0
            Get-ChildItem $from -Recurse  |
                Where-Object {(New-TimeSpan -Start  $_.LastWriteTime -End $cutoff).TotalHours -le 0} |
                Copy-Item -Destination {Join-Path $to $_.FullName.Substring($from.length)} -PassThru |
                ForEach-Object {$fileCount++}
            Write-Host ("Copied {0} files/folders successfully at {1}" -f $fileCount, $(Get-Date))

    Thanks again

    JonW


    JonW

    Monday, March 31, 2014 7:09 PM
  • Whatever floats your boat.  You're still using the pipeline in my suggestion, but it's ForEach-Object that's receiving the pipeline input instead of Copy-Item.  You still get all the streaming advantages.  If it really bothers you that much to use a named parameter, you could have also done this:

    ForEach-Object {$_ | Copy-Item -Destination {Join-Path $to $_.FullName.Substring($from.length)}; $fileCount++}

    Monday, March 31, 2014 7:23 PM
  • David -

    I think I get it now - I much prefer your approach: a very handy technique!!!

    Thanks

    Jonathan


    JonW

    Monday, March 31, 2014 7:46 PM