ExcelBDD.psm1

<#
.Description
Get worksheet from Excel file according to build sheet path and worksheet name
.Example
Get-ExcelWorksheet -ExcelPath C:\buildsheet.xlsx -WorksheetName 'PaaS SQL DB Build'
#>

function Get-ExcelWorksheet {
    param (
        [String]$ExcelPath,
        [String]$WorksheetName,
        [switch]$ExcelApplication
    )
    if (-not (Test-Path $ExcelPath)) {
        throw "$ExcelPath file does not exist."
    }
    # $ExcelApplication = $true
    if ($ExcelApplication) {
        $Worksheet = Get-ExcelWorksheetFromExcelApplication -ExcelPath $ExcelPath  -WorksheetName $WorksheetName
    }
    else {
        $Worksheet = Get-ExcelWorksheetFromImportExcel -ExcelPath $ExcelPath  -WorksheetName $WorksheetName
    }
    
    if ($null -eq $Worksheet ) {
        Close-ExcelWorksheet
        throw "$WorksheetName sheet does not exist."
    }
    return $Worksheet
}

function Get-ExcelWorksheetFromImportExcel {
    param (
        [String]$ExcelPath,
        [String]$WorksheetName
    )
    $script:appExcel = Open-ExcelPackage -Path $ExcelPath
    if ($WorksheetName) {
        $Worksheet = $appExcel.Workbook.Worksheets[$WorksheetName]
    }
    else {
        $Worksheet = $appExcel.Workbook.Worksheets[1]
    }
    $script:RowsCount = $Worksheet.Dimension.Rows
    $script:StartRow = $Worksheet.Dimension.Start.Row
    $script:ColumnsCount = $Worksheet.Dimension.Columns
    $script:StartColumn = $Worksheet.Dimension.Start.Column
    return $Worksheet
}

function Get-ExcelWorksheetFromExcelApplication {
    param (
        [String]$ExcelPath,
        [String]$WorksheetName
    )
    try {
        $script:appExcel = New-Object -ComObject Excel.Application
        # Let Excel run in the backend, comment out below line, if debug, remove below #
        # $script:appExcel.Visible = $true
        $WorkBook = $script:appExcel.Workbooks.Open($ExcelPath)
        if ($WorksheetName) {
            $Worksheet = $WorkBook.Sheets[$WorksheetName]
        }
        else {
            $Worksheet = $WorkBook.Sheets(1)
        }
        $Address = $Worksheet.UsedRange.Address() -split "\$"
        $script:RowsCount = $Worksheet.UsedRange.Rows.Count
        $script:StartRow = [int](($Address[2] -split ":")[0])
        $script:ColumnsCount = $Worksheet.UsedRange.Columns.Count
        $script:StartColumn = [int][char]$Address[1] - 64
    }
    catch {
        Write-Debug "MS Excel is not found, use ImportExcel instead."
        return Get-ExcelWorksheetFromImportExcel -ExcelPath $ExcelPath  -WorksheetName $WorksheetName
    }

    return $Worksheet
}

function Close-ExcelWorksheet {
    try {
        if ($script:appExcel.Name -eq "Microsoft Excel") {
            $script:appExcel.ActiveWorkbook.Close($false)
            $script:appExcel.Quit()
            [System.Runtime.Interopservices.Marshal]::ReleaseComObject($script:appExcel)
        }
        else {
            Close-ExcelPackage -ExcelPackage $script:appExcel -NoSave
        }
    }
    catch {
        Write-Debug "Excel is closed."
    }
}


<#
.SYNOPSIS
    Get a Hashtable list from excel sheet
.Description
    Get a Hashtable list from excel sheet, one row for one hashtable, duplicated header name will be added suffix "00"
.Example
    #Get TestcaseDataList for Pester Testcase
    $TestcaseDataList = Get-DataTable -WorksheetName SheetName `
        -ExcelPath "${StartPath}DataTableExample.xlsx" `
        -HeaderRow 1
    It "Full Rule Except Email From Excel File" -Testcases $TestcaseDataList {
        Test-MZIsPropertyValid -PropertyName $PropertyName -PropertyValue $PropertyValue -Rule $Rule | Should -Be ($Expected -eq "TRUE")
    }
 
.Example
    # Get TestcaseDataList from 1st sheet
    $TestcaseDataList = Get-DataTable -ExcelPath $ExcelFullPath
#>

function Get-DataTable {
    param (
        [string]
        # Specifies the excel file full path, or valid relative path
        $ExcelPath,
        [string]
        # Specifies the sheet name, if omit, the 1st sheet will be selected
        $WorksheetName,
        [int]
        # Header Row's number, if omit, it is 1
        $HeaderRow = 1,
        [string]
        # first column to read, if omit, it is A column
        $StartColumn = 'A',
        [string]
        # if StartColumn's value matches this matcher, this row will be collected, default is all
        $RowMatcher = ""
    )
    $Worksheet = Get-ExcelWorksheet -ExcelPath $ExcelPath -WorksheetName $WorksheetName
    
    
    $IntStartColumn = [int][char]($StartColumn.ToUpper()) - 64
    $StartRow = [int]$HeaderRow + 1
    #TODO find the all valid header
    $HeaderHashTable = @{}
    for ($iCol = $IntStartColumn; $iCol -lt ($script:StartColumn + $script:ColumnsCount); $iCol++) {
        $CurrentHeaderName = $Worksheet.Cells.Item($HeaderRow, $iCol).Text
        if ($CurrentHeaderName) {
            if (-Not [String]::IsNullOrEmpty($CurrentHeaderName.Trim())) {
                $HeaderHashTable[$iCol] = Get-HeaderName $HeaderHashTable $CurrentHeaderName.Trim()
            }
            else {
                break
            }
        }
        else {
            break
        }
    }
    $List = @()
    for ($iRow = $StartRow; $iRow -lt ($script:StartRow + $script:RowsCount); $iRow++) {
        $CurrentStartColumnText = $Worksheet.Cells.Item($iRow, $IntStartColumn).Text
        if (-Not [String]::IsNullOrEmpty($CurrentStartColumnText)) {  
            if ($CurrentStartColumnText -match $RowMatcher) {
                #This Row has values and matched
                $RowSet = @{}
                foreach ($iCol in $HeaderHashTable.Keys) {
                    $RowSet[$HeaderHashTable[$iCol]] = $Worksheet.Cells.Item($iRow, $iCol).Text
                }
                $List += $RowSet
            }
        }
        else {
            break
        }
    }
    Close-ExcelWorksheet | Out-Null
    return $List
}


function Get-HeaderName {
    param (
        $HeaderHashTable,
        $CurrentHeaderName
    )
    if ($HeaderHashTable.Values -NotContains $CurrentHeaderName) {
        return $CurrentHeaderName
    }
    else {
        $CurrentHeaderNameEnd = $CurrentHeaderName.substring($CurrentHeaderName.length - 2)
        if ($CurrentHeaderNameEnd -match "^\d{2}$") {
            $NewCurrentHeaderName = $CurrentHeaderName.substring(0, ($CurrentHeaderName.length - 2)) + ([int]$CurrentHeaderNameEnd + 1).ToString("00")
            Get-HeaderName $HeaderHashTable $NewCurrentHeaderName
        }
        else {
            Get-HeaderName $HeaderHashTable "${CurrentHeaderName}02"
        }
    }
}


function Show-ExampleList {
    param (
        [array]$ExampleList
    )
    $MaxLength = 60
    $ToBeShewFields = $ExampleList[0].Keys
    
    #Get the length of each field
    $ToBeShewFieldHashTable = @{}
    foreach ($field in $ToBeShewFields) {
        $ToBeShewFieldHashTable[$field] = $field.Length
    }
    foreach ($item in $ExampleList) {
        foreach ($field in $ToBeShewFields) {
            if (-not [String]::IsNullOrEmpty($item.$field)) {
                $ItemFieldLength = $item.$field.ToString().Length
                if (($ItemFieldLength -gt $ToBeShewFieldHashTable[$field]) -and ($ItemFieldLength -le $MaxLength)) {
                    $ToBeShewFieldHashTable[$field] = $ItemFieldLength
                }
                elseif ($ItemFieldLength -gt $MaxLength) {
                    $ToBeShewFieldHashTable[$field] = $MaxLength
                }
            }
        }
    }

    $MaxDashLine = "------------------------------------------------------------------------------------------------"
    #Show the Header
    $HeaderRow = "|"
    $AllDashLine = "-"
    $DividingLine = "|"
    $RowLength = -2
    foreach ($field in $ToBeShewFields) {
        $HeaderRow += (Get-FixedLengthString $field $ToBeShewFieldHashTable[$field]) + " |"
        $AllDashLine += (Get-FixedLengthString $MaxDashLine $ToBeShewFieldHashTable[$field]) + "--"
        $DividingLine += (Get-FixedLengthString $MaxDashLine $ToBeShewFieldHashTable[$field]) + "-|"
        $RowLength += ($ToBeShewFieldHashTable[$field] + 2)
    }

    Write-Host $AllDashLine
    Write-Host $HeaderRow
    Write-Host $DividingLine

    #Show the main contents in table
    $sb = [System.Text.StringBuilder]::new()
    $RowCount = $ExampleList.Count
    for ($i = 0; $i -lt $RowCount - 1 ; $i++) {
        $item = $ExampleList[$i]
        [void]$sb.Append("|")
        # }
        # foreach ($item in $ExampleList) {
        foreach ($field in $ToBeShewFields) {
            [void]$sb.Append( (Get-FixedLengthString $item.$field $ToBeShewFieldHashTable[$field]) + " |")
        }
        [void]$sb.AppendLine()
        [void]$sb.AppendLine($DividingLine)
    }
    #Show last Row
    if ($RowCount -eq 1) {
        $item = $ExampleList
    }
    else {
        $item = $ExampleList[$RowCount - 1]
    }
    
    [void]$sb.Append("|")
    foreach ($field in $ToBeShewFields) {
        [void]$sb.Append((Get-FixedLengthString $item.$field $ToBeShewFieldHashTable[$field]) + " |")
    }
    [void]$sb.AppendLine()
    [void]$sb.AppendLine($AllDashLine)
    [void]$sb.Append("|")
    [void]$sb.AppendLine((Get-FixedLengthString "Total Row Count: $RowCount" $RowLength) + " |")
    [void]$sb.AppendLine($AllDashLine)
    Write-Host $sb.ToString()
}

function Get-FixedLengthString {
    param (
        $Field,
        $FixedLength
    )
    if ([String]::IsNullOrEmpty($Field)) {
        $FieldLength = 0
    }
    else {
        $FieldLength = $Field.ToString().Length
    }
    if ($FieldLength -le $FixedLength) {
        $sb = [System.Text.StringBuilder]::new()
        [void]$sb.Append($Field)
        for ($i = 0; $i -lt ($FixedLength - $FieldLength); $i++) {
            [void]$sb.Append(" ")
        }  
        $FixedLengthField = $sb.ToString()
    }
    else {
        $FixedLengthField = $Field.ToString().Substring(0, $FixedLength)
    }
    return $FixedLengthField
}

<#
.SYNOPSIS
    Get hashtable list of Example data
 
.Description
    Every row in the excel sheet will be one Hashtable
 
.Example
    Describe "Test Get-ExampleList" {
        use default HeaderRow which is 1, and default ParameterNameColumn which is C
        $ExampleList = Get-ExampleListByHeader -ExcelPath ".\Excel\Example1.xlsx" -WorksheetName 'Scenario1'
        It "Easy Success of SBE" -TestCases $ExampleList {
            [int]$BlackSweaterCountAtCustomer | Should -BeGreaterOrEqual $BlackSweaterCountReturned
            [int]$BlackSweaterCountInInvertory1 + [int]$BlackSweaterCountReturned | Should -Be $BlackSweaterCountInInvertory2
        }
    }
 
.Example
    Describe "Test filter the dashboard by department" {
        $ExampleList = Get-ExampleListByHeader -ExcelPath $ExcelBDDFilePath `
            -WorksheetName 'StoryExample1' `
            -ParameterNameColumn E `
            -HeaderRow 3
        It "Run Example one by one" -TestCases $ExampleList {
            #The below variables are generated automatically from Excel
            Write-Host "===$Header==="
            Write-Host $SelectedView
            Write-Host $DepartmentCount
            Write-Host $SelectedDepartment
            Write-Host $FullDepartmentName
            Write-Host $DepartmentLocation
            Write-Host $DepartmentCurrentMonthKPI1
            Write-Host $DepartmentCurrentMonthKPI2
        }
    }
#>

function Get-ExampleListByHeader {
    param (
        [string]$ExcelPath,
        [string]$WorksheetName,
        [int]$HeaderRow = 1,
        [string]$ParameterNameColumn = 'C',
        [string]$HeaderMatcher,
        [string]$HeaderUnmatcher,
        [switch]$Expected,
        [switch]$TestResult
    )
    $Worksheet = Get-ExcelWorksheet -ExcelPath $ExcelPath -WorksheetName $WorksheetName
    return Get-ExampleListFromWorksheet -Worksheet $Worksheet `
        -HeaderRow $HeaderRow `
        -ParameterNameColumn $ParameterNameColumn `
        -HeaderMatcher $HeaderMatcher `
        -HeaderUnmatcher $HeaderUnmatcher `
        -Expected:$Expected `
        -TestResult:$TestResult
}


function Get-ExampleListFromWorksheet {
    param (
        $Worksheet,
        [int]$HeaderRow,
        [string]$ParameterNameColumn,
        [string]$HeaderMatcher,
        [string]$HeaderUnmatcher,
        [switch]$Expected,
        [switch]$TestResult
    )

    if ($TestResult) {
        $ColumnStep = 3
        $CurrentRow = $HeaderRow + 2
    }
    elseif ($Expected) {
        $ColumnStep = 2
        $CurrentRow = $HeaderRow + 2
    }
    else {
        $ColumnStep = 1
        $CurrentRow = $HeaderRow + 1
    }
    $ParamNameCol = [int][char]($ParameterNameColumn.ToUpper()) - 64
    #Get Test data set Column Array
    $CurrentCol = $ParamNameCol + 1
    $ColumnNumArray = @()
    while (-not [String]::IsNullOrEmpty($Worksheet.Cells.Item($HeaderRow, $CurrentCol).Text)) {
        if ($HeaderMatcher -and (-not $HeaderUnmatcher)) {
            if ($Worksheet.Cells.Item($HeaderRow, $CurrentCol).Text -match $HeaderMatcher) {
                $ColumnNumArray += $CurrentCol
            }
        }
        elseif ((-not $HeaderMatcher) -and $HeaderUnmatcher) {
            if ($Worksheet.Cells.Item($HeaderRow, $CurrentCol).Text -notmatch $HeaderUnmatcher) {
                $ColumnNumArray += $CurrentCol
            }
        }
        elseif ($HeaderMatcher -and $HeaderUnmatcher) {
            if (($Worksheet.Cells.Item($HeaderRow, $CurrentCol).Text -match $HeaderMatcher) `
                    -and ($Worksheet.Cells.Item($HeaderRow, $CurrentCol).Text -notmatch $HeaderUnmatcher)) {
                $ColumnNumArray += $CurrentCol
            }
        }
        else {
            $ColumnNumArray += $CurrentCol
        }
        $CurrentCol += $ColumnStep
        if ($CurrentCol -ge ($script:StartColumn + $script:ColumnsCount)) {
            break
        }
    }

    #Get Parameter Row Array
    $RowNumArray = @()
    for ($iRow = $CurrentRow; $iRow -lt ($script:StartRow + $script:RowsCount); $iRow++) {
        if (-Not [String]::IsNullOrEmpty($Worksheet.Cells.Item($iRow, $ParamNameCol).Text)) {
            if ("NA" -ne $Worksheet.Cells.Item($iRow, $ParamNameCol).Text) {
                $RowNumArray += $iRow
            }
        }
    }

    $List = [System.Collections.ArrayList]::new()
    foreach ($iCol in $ColumnNumArray) {
        $DataSet = [ordered]@{}
        #Put Header
        $DataSet["Header"] = $Worksheet.Cells.Item($HeaderRow, $iCol).Text.Trim()
        foreach ($iRow in $RowNumArray) {
            $DataSet[$Worksheet.Cells.Item($iRow, $ParamNameCol).Text.Trim()] = $Worksheet.Cells.Item($iRow, $iCol).Text
            if ($TestResult -or $Expected) {
                $DataSet[$Worksheet.Cells.Item($iRow, $ParamNameCol).Text.Trim() + "Expected"] = $Worksheet.Cells.Item($iRow, $iCol + 1).Text
                if ($TestResult) {
                    $DataSet[$Worksheet.Cells.Item($iRow, $ParamNameCol).Text.Trim() + "TestResult"] = $Worksheet.Cells.Item($iRow, $iCol + 2).Text
                }
            }
        }
        [void]$List.Add($DataSet)
    }
    Close-ExcelWorksheet | Out-Null
    return $List
}


<#
.SYNOPSIS
Get hashtable list of Example data
 
.Description
One Hashtable from one example data area in excel sheet
 
.Example
Describe "Test Get-ExampleList" {
    $ExampleList = Get-ExampleList -ExcelPath ".\Excel\Example1.xlsx" -WorksheetName 'Scenario1'
    It "Easy Success of SBE" -TestCases $ExampleList {
        [int]$BlackSweaterCountAtCustomer | Should -BeGreaterOrEqual $BlackSweaterCountReturned
        [int]$BlackSweaterCountInInvertory1 + [int]$BlackSweaterCountReturned | Should -Be $BlackSweaterCountInInvertory2
    }
}
 
.Example
Describe "Test filter the dashboard by department" {
    $TestcaseList = Get-ExampleList -ExcelPath $ExcelBDDFilePath `
        -WorksheetName 'StoryExample1'
    It "Run Example one by one" -TestCases $TestcaseList {
        #The below variables are generated automatically from Excel
        Write-Host "===$Header==="
        Write-Host $SelectedView
        Write-Host $DepartmentCount
        Write-Host $SelectedDepartment
        Write-Host $FullDepartmentName
        Write-Host $DepartmentLocation
        Write-Host $DepartmentCurrentMonthKPI1
        Write-Host $DepartmentCurrentMonthKPI2
    }
}
#>

function Get-ExampleList {
    param (
        [string]$ExcelPath,
        [string]$WorksheetName,
        [string]$HeaderMatcher,
        [string]$HeaderUnmatcher
    )
    $Worksheet = Get-ExcelWorksheet -ExcelPath $ExcelPath -WorksheetName $WorksheetName
    for ($iRow = $script:StartRow; $iRow -lt ($script:StartRow + $script:RowsCount); $iRow++) {
        for ($iColumn = $script:StartColumn; $iColumn -lt ($script:StartColumn + $script:ColumnsCount); $iColumn++) {
            if ($Worksheet.Cells.Item($iRow, $iColumn).Text -match "^Param.*Name") {
                # [int][char]($ParameterNameColumn.ToUpper()) - 64
                $ParameterNameColumn = [string][char]($iColumn + 64)
                if ($Worksheet.Cells.Item($iRow, $iColumn + 1).Text -match 'Input') {
                    $HeaderRow = $iRow - 1
                    if ($Worksheet.Cells.Item($iRow, $iColumn + 3).Text -match 'Test Result') {
                        $TestResult = $true
                    }
                    else {
                        $Expected = $true
                    }
                }
                else {
                    $HeaderRow = $iRow
                }
                Break
            }
        }
        if ($HeaderRow) {
            Break
        }
    }
    if (-Not $HeaderRow) {
        throw "Parameter Name grid is not found"
    }
    return Get-ExampleListFromWorksheet -Worksheet $Worksheet `
        -HeaderRow $HeaderRow `
        -ParameterNameColumn $ParameterNameColumn `
        -HeaderMatcher $HeaderMatcher `
        -HeaderUnmatcher $HeaderUnmatcher `
        -Expected:$Expected `
        -TestResult:$TestResult
}