Pages

Tuesday, June 21, 2016

PowerShell v3 in a Year Day 7 about If

about_If is a bit of a softball, but, I figured, hey, it needs to be covered. In fact, all of the logic constructs need to be touched on. This just happened to catch my eye today. As I was reading in a book on web security testing yesterday, programming really comes into play when logic and variable responses to actions are available to a web page, program, script, etc. In short, if you render the same HTML page as static content, no web application testing is possible because there is no web application. Its purely a static page server. With that in mind, if is one of the most basic programming constructs to transform a purely declarative set of statements into a program which has to respond to multiple possibilities and with which comes the first level of complexity in programming.

In Powershell, if is "a language command you can use to run statement lists based on the results of one or more conditional tests." Straight from the doco. In general, if is used, in best practices, with small sets of possibilities. So, there are really only a few scenarios where you need/want to use if:

  • when a simple boolean response is necessitated. That is, your script/command has to respond in a yes/no, true/false fashion.
  • when a small set of similar, but, related conditions can be met. By small set, I tend to think of anywhere between 3 and 8. Once you start sprawling past 5 or 6, switch is really something you may want to start considering. I will explore more in detail what I am getting at here later.
When using if in PowerShell, you can provide multiple code blocks for usage if/when a given condition is met. One thing to know about if/then is that you will get one response from an if/elseif/else tree. So, out of X possibilities, you will only get one selection. To demonstrate this, I will use a simple tree with four conditions, three of which are identical, and, a fourth, the last else, which is really just a catch all.
if(1)
{
    1
}
elseif(1)
{
    2
}
elseif(1)
{
    3
}
else
{
    4
}
When this runs, I get 1. Since the 1st, 2nd and 3rd conditions are identical, it simply grabs the first one block and runs with it. In essence, the point here is about mutual exclusivity of conditions and if/elseif/else trees. There are other constructs which provide different possibilities, but, when you use if, known you are locking yourself into one (or no) specific responses and thats that. Ok, enough of that point....

As the shell runs through the condition checks, it examines the object/state being checked. If it evaluates to true, the following block executes. Otherwise, it falls through to the next condition check. If none evaluate to true, no response, as provided by the condition blocks, is handled. The basic syntax, straight from the help, is
 if (<test1>)
    {<statementlist 1>}
[elseif (<test2>)
    {<statementlist 2>}]
[else
    {<statementlist 3>}]
Using the example, if test1 succeeds, statement list 1 fires and the if statement is exited. If test 1 fails, test 2 is run. Similarly, if test 2 succeeds, statement list 2 fires and the if statement is exited. This logic repeats for each test provided in the if statement until either

  1. something returns true (and executes a statement list) or 
  2. all tests are run, none pass, and, nothing happens.
There is not theoretical limit to the number of ElseIf statements you may include in a block. Just understand, by design, these can get a bit unwieldy to maintain and hard to follow unless there is some prevailing logic controlling the checks. Back to my best practices statement, I see a few specific things here:
  1. There are specific, mathematical permutations which may dictate a fixed number of controlled conditions worth testing. Lets say, for instance, you have three things for which you want to exhaustively test. Using a factorial calculation, you evaulate 3! to yield 6 possible combinations of tests. In this case, 6 is a little high for a number of conditions to if/elseif/elseif.../else. However, since you know exactly what you are dealing with, the number is in a way irrelevant. You are testing all possible conditions, so, the coverage is comprehensive and the if statement is a conclusive testing structure based on logic alone.
  2. There are case-based conditions which are strictly determined by an object property set, a problem design or scenario which strictly controls the number of possibilties. Again, this will lean more back towards the purely mathematical combination route, where, your test will, in essence, be determined by a limited number of possibilities determined explicitly by your test. for instance, if you want to test that a folder either exists (or not) there are two tests there. Tack on top of that to see if the folder has children (or not) there are two more tests. But, in reality, we have three tests.
To illustrate this second set of possibilities, look at this test tree. There are only three tests, but, a fourth one, by logic, is unnecessary, as noted in the comments.
if((Test-Path -Path $path) -and([System.IO.Directory]::GetFiles($path).Count -eq 0))
{
    # the folder exist and has no files    statement list1}elseif((Test-Path -Path $path) -and([System.IO.Directory]::GetFiles($path).Count -gt 0))
{
    # the foler exists and has files    statement list2}elseif(!(Test-Path -Path $path))
{    # the folder does not exist - logically, it cannot have files if it does not exist, so, no 4th test    statement list3
I know this is probably a bit over the top for a simple exploration of the if statement, but, in daily practice, you learn little things that may not be obvious at first after enough time you start to figure them out and apply them as either time savers, logical outgrowths, etc.

One of the more interesting ones I work with, but, dont remember often enough, is to let the condition itself serve as both the test AND the response. Now, I know this may sound a little odd, but, a demonstration my help. Here is an example:
functionTest-FolderExists
{
    param(
        [Parameter(
            Mandatory =$true
        )]
        $path
    )

    Test-Path -Path$path -PathTypeContainer
}

Test-FolderExists-Path C: est
In the body of the function Test-Path will return a boolean depending on if the conditions are met or not. In this case, I do not need to write two tests/statement lists. I can simply let the test itself handle/determine the response. In essence, the if/then nature of the statement is implied and does not need to be added to the function. (Thanks to Josh Miller and Justin Rich for being patient enough to help me get it into my mental catalog.) So, a short test like this can be reduced, in essence, to a one liner, as opposed to several, simply by letting the function itself do the work of answering the question.

Below are some basic examples of using the if/elseif/else statements.

Example 1: if
if($a -gt2)
{
    Write-Host "The value $ais greater than 2."
}
Example 2: if/else
if($a -gt2)
{
    Write-Host "The value $ais greater than 2."
}
else
{
    Write-Host "The value $ais less than or equal to 2, is not created or is not initialized."
}
Example 3:  if/elseif/else
 if($a -gt2)
{
    Write-Host "The value $ais greater than 2."
}
elseif($a -eq2)
{
    Write-Host "The value $ais equal to 2."
}
else
{
    Write-Host "The value $ais less than 2 or was not created or initialized."
}
Takeaway

Again, this way never really meant to be a long, sprawling topic, but, still good to cover. One of the key tricks I learned is the use of the condition to eliminate the need for writing a statement list. If all you want, from a given function/test, is a bool, let the condition be the test AND the response. That is a nice, simple trick that can save you a few minutes here and there avoiding writing code you get directly from implication. For further reading you can check out:

  • about_Comparison_Operators
  • about_Switch


<

Related Posts by Categories

0 comments:

Post a Comment