Powershell design issue - too many if(deviceId)

Viewed 51

I'm writing a Powershell script, and it reads in files for each device version, and some of the files are different format, so I have to post-process the files to get the correct format, or handle it differently due to different data. So I have about 2 groups of 12 if statements (for 12 devices) to handle the differences. If it was object oriented, I would have classes to handle these, but I couldn't get the object oriented PowerShell working. Does anyone have any ideas how to handle this sort of thing? My list of devices has grown from 3 to about 20 (I have about 8 left), and I hadn't foreseen this at the start that more would be given to Parse, causing spaghetti code.

My code looks like this:

     if($currentVersion -Match '^(68)') 
     {
          #special code for 68
          #prepare Map for ErrorCodeList and Error Descriptions before loop through each error
          $errorCodeListFile = Join-Path -Path $pathViewBase -ChildPath 
          $errorCodeListBase68
      

          #call method to return basePathFull cppFile method contents
          $matchFound = Get-MethodContents -codePath $File[0] -methodNameToReturn "Device68::GetFromCode" -followingMethodName "Device4=68::SkipIt" 
          #call method to get case info
          $resultsCase = (Get-CaseContents68 -parsedCaseMethod $matchFound )  
      }
      if($currentVersion -Match '^(30)')
      {
          $HopResultMap = [ordered]@{}
          $HopResultMap2 = [ordered]@{}
          #prepare Map for ErrorCodeList and Error Descriptions before loop through each error
          $errorCodeListFile = Join-Path -Path $pathViewBase -ChildPath $errorCodeListBase30  
          #call method to return basePathFull cppFile method contents
          $matchFound = Get-MethodContents -codePath $File[0] -methodNameToReturn "30Device::GetFromCode" -followingMethodName "30Device::GetFirst" #
          #call method to get case info
          $HopResultMap = (Get-Contents30 -fileContent $matchFound)  
          $FileHandler = Join-Path -Path $basePathFull -ChildPath "30Device\30Device.h"
          $matchFound2 = Get-MethodContents -codePath $FileHandler -methodNameToReturn "NoError" -followingMethodName "PageID;"
          $HopResultMap2 = Get-Contents30_b -fileContent $matchFound2 
          #combine the data structures so can reuse 70 process flow/variables


          $resultsCase = foreach ($kvp in $HopResultMap.GetEnumerator()){ #foreach key in $HopResultMap[1]
          Write-Host $kvp.sdkDesc
          Write-Host $kvp.sdkOutErr
          Write-Host $HopResultMap2[$kvp.sdkErrKey]
          [PSCustomObject][ordered]@{
                sdkErr      = $HopResultMap2[$kvp.sdkErrKey]
                sdkDesc     = $kvp.sdkDesc
                sdkOutErr   = $kvp.sdkOutErr
          }
          }#resultsCase
       }
       #etc
       #plus another section like this later for special processing needed later for different data pieces

It's not all different code for the different device versions. There is about 1000+ lines shared, plus shared functions, and also separate functions for each version to handle reading different files/layouts.

I am using PowerShell 5.1 and VSCode.

1 Answers

Even the question is very abstract due to the fact that there are no details about what is actually being invoked inside the if statements, let me try to answer this with an abstract answer as I believe that the solution for this general (and not just PowerShell) programming issue lays in a

decision table

(as defined later in the Wiki: program embedded decision tables).

For this -in PowerShell-, you might use a csv file (Import-Csv) or (embedded) csv format (ConvertFrom-Csv), an Excel module, a database or a custom functions as ConvertFrom-SourceTable for better readability:

$DecisionTable = ConvertFrom-SourceTable '
Version Do1 Do2 Do3
------- --- --- ---
^(30)   X       X
^(68)   X
^(70)       X   X'

Or even share and scrape your decision table from the internet/intranet with a custom function as Read-HtmlTable:

$WikiTable = Read-HtmlTable https://en.wikipedia.org/wiki/Decision_table -Table 2

With that, you might consider to use the native Invoke-Expression cmdlet, something like:

$WikiTable.Where{
    $currentVersion -Match $_.Input 
}.ForEach{
    Invoke-Expression $_.'Function Pointer'
}

But taken the warning:

Caution

Take reasonable precautions when using the Invoke-Expression cmdlet in scripts. When using Invoke-Expression to run a command that the user enters, verify that the command is safe to run before running it. In general, it is best to design your script with predefined input options, rather than allowing freeform input.

I would recommend against this and try to translate your functions into parameter which you handle in a general function with hardcoded invoke conditions, like:

function Do1 { Write-Host 'Do 1' }
function Do2 { Write-Host 'Do 2' }
function Do3 { Write-Host 'Do 3' }

$DecisionTable.Where{
    $currentVersion -Match $_.Version 
}.ForEach{
    if ($_.Do1) { Do1 }
    if ($_.Do2) { Do2 }
    if ($_.Do3) { Do3 }
}
Related