Words: 853
Time to read: ~ 4.5 minutes
ForEach-Object
I have a quick function just to check the total size of full, differential, and log backups in a folder. It’s handy because we group the database backups into a folder per database and we label all backups files based on what type of backup we’re taking e.g. full = “.bak”, diff = “.diff”, and log = “.trn”.
If you’re interested, the function code is here…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Function Get-BackupFilesSizes { | |
$Bak = $Diff = $Trn = 0; | |
$Bak = (Get-ChildItem –File | | |
Where-Object { $_.Extension -eq '.bak' } | | |
Measure-Object –Property Length –Sum).Sum; | |
$Diff = (Get-ChildItem –File | | |
Where-Object { $_.Extension -eq '.diff' } | | |
Measure-Object –Property Length –Sum).Sum; | |
$Trn = (Get-ChildItem –File | | |
Where-Object { $_.Extension -eq '.trn' } | | |
Measure-Object –Property Length –Sum).Sum; | |
[PSCustomObject]@{ | |
'Bak Files (GB)' = $Bak / 1GB | |
'Diff Files (GB)' = $Diff / 1GB | |
'Trn Files (GB)' = $Trn / 1GB | |
}; | |
}; | |
-Begin
Now this is not an advanced function.
There’s no mention of [cmdletbinding()]
and no mention of param()
in there.
While it uses Get-ChildItem
, there’s no mention of the -Path
parameter so it only works on the current directory you’re on.
All that being said, it does exactly why I ask for when I run it in the correct location.

However, on this server, there are 20 different database folders and I want a list of all of them. So how do I do that?
Again, I don’t have a $path parameter (I swear I’ll add it in eventually) so I have to be physically in the directory before I can run my function.
Simple enough, we just add a Get-ChildItem
, do a ForEach-Object
, and then run my function.
However, with quick scripts, I don’t come from a Linux background so I don’t normally use ls
or a Windows command line background so I don’t normally use dir
either. I come from a Windows GUI background so the closest I get to shortcuts are not naming the parameters.
Get-ChildItem | ForEach-Object {Set-Location $_.FullName; Get-BackupFilesSize };

What’s this? Ah damn, after everything is done we are still in one of the database directories instead of the parent folder. I know it’s a small thing but it annoyed me so I aimed to fix the above line to account for it.
It is here that we meet our foe!
-Process
I’ve read enough about PowerShell to know that ForEach-Object
has an -End
parameter that gets run after everything in the -Process
parameter has run. Therefore I can just use that to go back to the parent directory after I’m done running my function on all the database folders!

Specifies a script block that runs after this cmdlet processes all input objects
Now I happen to know that logically -End
is at the end so it should come after the -Process
scriptblock. So I modify my ad-hoc script above and tack on the -End
parameter’s scriptblock to go back to the parent drectory.
Get-ChildItem | ForEach-Object {Set-Location $_.FullName; Get-BackupFilesSize } {Set-Location ..}

Eh…what?
Cannot process argument because the value of argument “path” is null.
The “path” argument is NULL? And I’m back to the very root of “I:\”?
Thankfully, at this stage, I know to go back and read the help file again before I let slip any curses.

Hmm…the only thing I can see here is that it’s not a positional parameter. I know -Process
is though, right? I mean if it isn’t then nearly every script I’ve seen should fail…

There’s also a -Begin
parameter. This scriptblock runs only once before the -Process
scriptblock. It, like the -End
scriptblock, isn’t required either.
Specifies a script block that runs before this cmdlet processes any input objects.

But again, that position is named so that shouldn’t cause any problems, I assume…
Luckily, I’m currently reading “Windows PowerShell In Action” by Bruce Payette ( twitter ) and this starts to ring a bell. So I go back and check out the section in the book on ForEach-Object
and a little gem starts to shine out:
If -Begin is empty and -Process has more than two scriptblocks in the collection, then the first one is treated as the -Begin scriptblock and the second one is treated as the -Process scriptblock
Ah so it defaults that way!
That explains why I got the error message only once, and why I got all the way back to the “I:\” drive!
My first scriptblock, the one with my function ran once at the start, and then my second scriptblock, the one saying go back up a folder, ran around 20 times!
So if that’s the case, all we need to do is specify that the -End
scriptblock is actually the -End
scriptblock!
Get-ChildItem | ForEach-Object { Set-Location $_.FullName; Get-BackupFilesSize } -End { Set-Location .. };

-End
I don’t know if any other PowerShell cmdlet has this little quirk, especially since I haven’t finished the book yet. It would be interesting to see what does though.
I do know that it would have taken one metric tonne of testing to figure this little peculiarity out for myself. Thankfully, there are loads of resources out there to help with learning, and I have all the time in the world to keep learning.
Nice to know it’s already starting to pay off though 🙂
One thought on “[PowerShell] Name Parameters When Using ForEach-Object…or else!”