Products.psm1

<#
.SYNOPSIS
    This function retrieves a hash table of all products which are authorized for the Tech Data partner.
.DESCRIPTION
    This function retrieves a hash table of all products which are authorized for the Tech Data partner.
    The products returned from this function do not contain all their details.
    For retrieving products with their full details, use the Get-TechDataProductDetails function.
.PARAMETER apiHeaders
    The Tech Data API headers generated by Get-TechDataRestApiHeaders.
.PARAMETER environment
    The environment to which the call will be made, either production or test.
.PARAMETER outputProgressUpdates
    Whether to output progress updates.
.PARAMETER useCachedResult
    Whether to use the previously cached result (if present).
#>

function Get-TechDataProduct {
    param (
        # The Tech Data API headers generated by Get-TechDataRestApiHeaders.
        [Parameter(Mandatory=$false, ValueFromPipeline=$true)]
        [ValidateNotNull()]
        $apiHeaders = $Global:TechDataApiHeaders,

        # The environment to which the call will be made, either production or test.
        [Parameter(Mandatory=$false)]
        [ValidateSet("production", "test")]
        [String]$environment = "test",

        # Whether to output progress updates.
        [Parameter(Mandatory=$false)]
        [Switch]$outputProgressUpdates,

        # Whether to use the previously cached result (if present).
        [Parameter(Mandatory=$false)]
        [Switch]$useCachedResult
    )

    # Use cached result
    if ($useCachedResult -and $Global:TechDataProduct_CachedProductHashTable) {
        return $Global:TechDataProduct_CachedProductHashTable
    }

    try {
        # Set the protocol to TLS 1.2
        [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12

        $urlTemplate = Get-TechDataApiEndpointUrl -GetProduct -Environment $environment

        # Create hash table to store vendor results
        $vendors = @{}

        # Call the API for the first time only on page 1 as we don't know how many pages of records there are
        if ($outputProgressUpdates) {
            Write-Information "Retrieving Tech Data product page 1."
        }
        $url = $urlTemplate -f 1

        # Try to call the API
        $response = Invoke-RestMethodWithRetry -Uri $url -Headers $apiHeaders -Method "GET" -IntervalMilliseconds 500 -MaximumNumberOfCalls 20

        # Add to table of vendors
        # Trim of vendor name is necessary as several vendors were found to have extra whitespace in names
        # eg. "Microsoft " instead of "Microsoft"
        foreach ($vendor in $response.BodyText.products.vendors) {
            $vendors.($vendor.vendorName.Trim()) = $vendor.listings
        }

        # Call the API for the rest of the pages, starting from page 2
        $totalPages = $response.BodyText.products.totalPages
        for ($pageNumber = 2; $pageNumber -le $totalPages; $pageNumber += 1) {
            if ($outputProgressUpdates) {
                Write-Information "Retrieving Tech Data product page $($pageNumber)/$($totalPages)."
            }

            # Set the page number for the API call
            $url = $urlTemplate -f $pageNumber

            # Try to call the API
            try {
                $response = Invoke-RestMethod -Uri $url -Headers $apiHeaders -Method "GET"
            }

            # Error while calling the API
            catch {
                Write-Error "Error while invoking API. `r`n$($_.Exception.Message)"
                return $null
            }

            # Check if call was a success
            if ($response.Result -eq "Failed") {
                Write-Error "API call failed with error code $($response.ErrorCode) and error message '$($response.ErrorMessage)'."
                return $null
            }

            # Add to table of vendors
            foreach ($vendor in $response.BodyText.products.vendors) {
                # Vendor is already in the table
                if ($vendors.ContainsKey($vendor.vendorName.Trim())) {
                    $vendors.($vendor.vendorName.Trim()) += $vendor.listings
                }
                else {
                    $vendors.($vendor.vendorName.Trim()) = $vendor.listings
                }
            }
        }

        # Consolidate vendor SKUs
        $vendors = $vendors.GetEnumerator() | ForEach-Object {
            # Format SKUs into a hash table
            $vendorSkus = @{}

            # Iterate through all the vendor listings
            foreach ($listing in $_.Value) {
                # Iterate through the SKUs in each listing
                foreach ($sku in $listing.skus) {
                    # Add vendor name to the SKU object
                    $sku | Add-Member -NotePropertyName "VendorName" -NotePropertyValue $_.Key

                    # Add listing name to the SKU object
                    $sku | Add-Member -NotePropertyName "ListingName" -NotePropertyValue $listing.listingName

                    # Add the SKU to the hash table
                    $vendorSkus.Add($sku.sku, $sku)
                }
            }

            # Return the SKUs
            [PSCustomObject]@{
                VendorName = $_.Key
                VendorSkus = $vendorSkus
            }
        }

        # Convert list into hash table
        $skuHashTable = @{}
        foreach ($vendor in $vendors) {
            $skuHashTable.Add($vendor.VendorName, $vendor.VendorSkus)
        }

        # Cache and return the result
        $Global:TechDataProduct_CachedProductHashTable = $skuHashTable
        return $Global:TechDataProduct_CachedProductHashTable
    }
    catch {
        Write-Error "Exception occurred on line $($_.InvocationInfo.ScriptLineNumber): `r`n$($_.Exception.Message)"
        return $null
    }
}