Immediately see what your PowerShell script does – move the main code to the top
I’m currently updating/rewriting a whole slew of PowerShell scripts that I have created over the last couple years into something less “hacky”.
As scripts grow larger they become harder to read or understand the logic. A typical and recommended approach is to break it up and separate certain functionality or actions into functions.
One thing that always bugged me on that approach was, that if your start using functions in your PowerShell script, they have to be defined first. Which means, that your “real” code, or better that part that contains the main script logic is somewhere near the end. So one has to scroll down in the script to some point, where the function definitions end and the “real” script part starts. Not saying that the functions don’t contain any real code, they just serve a certain and very specific purpose. If you want to know what exactly a function does, you scroll to the function itself. But here we want to know what the script iteself is intended to do.
One typical approach is to move regularly used functions into a separate file, e.g. a PowerShell module. This way they are just being called from the script. However, this comes with several “drawbacks” (which might not really be drawbacks, depending on the situation 😉 ). Besides having different scopes, the biggest one for the scripts I’m talking about right now is, that I want those to be standalone scripts. I don’t want to have a dependency on additional files. So even if that means I have duplicated code within those scripts, I tend to have already existing functions copied to this script, rather than referencing a module. As said, that’s not a general rule, it’s just for this specific purpose. Also it’s normally not feasable to put every function into a separate module, as some are script specific, so one would bloat the module with unneeded junk.
A second option would be to define a function that contains all the main code. Let’s call it “Main” and have it on top of the script:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
[CmdletBinding()] PARAM ( [Parameter(Mandatory)] [string[]] $ComputerName ) function Main { foreach ($Computer in $script:ComputerName) { Write-Host $Computer } } Main |
This approach has a nice benefit, if it comes to testing scripts (I’ll get back to this in a later post). However, you need to take care about proper script parameter/variable handling, as the function has a different scope than the script itself. One way would be to create another set of parameters for the function that is identical to the parameters of the script and then just pass the exact same parameters to the main function. But that just unnecessarily duplicates that code. Another option would be as shown above, to explicitly use the parameters in the script scope to avoid any unexpected side effects.
However, the initial call to “Main” is still at the script bottom. How about making use of the advanced functions in PowerShell, explicitly the input processing methods? They allow us to use a Begin, Process and End block within our script/function. It’s meant to be used with a pipeline to explictly execute some code before and/or after the processing of pipeline objects. However it allows us also, to re-use it for our purpose, as the order of those blocks within the script isn’t mandatory. So let’s take our example from above and use a Begin and Process block:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
[CmdletBinding()] PARAM ( [Parameter(Mandatory)] [string[]] $ComputerName ) Process { $ComputerName | Do-Something } Begin { function Do-Something{ [CmdletBinding()] PARAM ( [Parameter(ValueFromPipeline)] [string] $ComputerName ) Begin { Write-Host "Start Do-Something" } Process { Write-Host $ComputerName } End{ Write-Host "Finished Do-Something" } } } |
So what is happening here?
First, the script is called with the exact same parameters as the former sample (an array of computernames).
Then the Begin block of the script is executed (lines 12 to 22) even if it’s defined after the Process block and by this all the functions of the script are parsed and available. After that the Process Block of the script is executed which now takes the input of the $ComputerName parameter and pipes it to the Do-Something function.
In the Do-Something function I explicitly showed the typical usage of the Begin, Process and End blocks. This time, they are in the order of execution, but as we have seen on script level, that’s not mandatory.
Not going into more detail about advanced functions here as the main intention was to show a neat way of having the main code of your script right on top when you open it and not have to scroll through hundreds of lines of code to figure out what exactly this script is actually doing.
Recent Comments