Get-LenovoInfo.ps1

<#PSScriptInfo
 
    .VERSION 1.8.0
    .GUID 2ec72304-ff34-4f42-bd0c-2211df4d9074
    .AUTHOR Erlend Westervik
    .COMPANYNAME
    .COPYRIGHT
    .TAGS Device, Warranty, Lenovo, Hardware, Age, Warranties, Endpoint, Management, Serialnumber, Serial, Product, Model, Manufacturer, Specification, Specc
    .LICENSEURI
    .PROJECTURI https://github.com/erlwes/Get-LenovoInfo
    .ICONURI
    .EXTERNALMODULEDEPENDENCIES
    .REQUIREDSCRIPTS
    .EXTERNALSCRIPTDEPENDENCIES
    .RELEASENOTES
        Version: 1.0.0 - Original published version
        Version: 1.1.0 - Rewrite. New baseline.
        Version: 1.5.0 - Added 'ShowCachesCombined' + fixed some output that got pre-formated
        Version: 1.6.0 - Added 'VerboseLogging'-parameter and changed default cache location to script location instead of current directory.
        Version: 1.8.0 - Added PostWarrantyService/ContractWarranties (extended support) to the results.
#>


<#
.SYNOPSIS
    Get product specifications and warranty information of Lenovo device using serialnumber.
 
.DESCRIPTION
    The scripts will create a local CSV-file cache, and then add warranty and/or product specifications for every serialnumber that is queried, and store it to the cache.
    If the serialnumber/model-code does not exist in cache (eg. first time queried), the script will go online and fetch warranty and/or product spec info from Lenovo web-services.
    In addition to the standard output from Lenovo web-pages, days left of warranty or days since expired will be calculated, alog with an aproximate age of the computer using (years since waranty start)
 
.PARAMETER Serialnumber
    One or more serialnumber(s) that you want to get warranty and/or product specs for.
 
.PARAMETER Type
    What type of information to query. This can be 'Warranty' or 'ProductSpecification'. Default 'Warranty'.
 
.PARAMETER Brief
    Warranty: Shows only base product warranties. If more than one, show premium/on-site support warranty
    Product: Output limited to the most essential hardware components of product
 
.PARAMETER InspectCache
    Shows content in exising offline cache file(s). Pipe results to gridview for easy filtering and searching.
 
.PARAMETER ClearCache
    Deletes one or more offline cache file(s) from disk.
 
.PARAMETER ShowCachesCombined
    Shows selected info on all devices that exist in local cache. Pipe results to gridview for easy filtering and searching.
 
.PARAMETER VerboseLogging
    Logging to console.
 
.EXAMPLE
    .\Get-LenovoInfo.ps1 -Serialnumber 'PF0A0BBB' -Type Warranty
     
    This example will return the product warranties for the Lenovo computer with serialnumber 'PF0A0BBB'
 
.EXAMPLE
    .\Get-LenovoInfo.ps1 -Serialnumber 'PF0A0BBB' -Type ProductSpecification -Brief | Format-Table *
     
    This example will return a brief overview of the product specifications of a Lenovo computer with serialnumber 'PF0A0BBB'
#>

param(
    [Parameter(Mandatory = $False, ValueFromPipeline = $true)]
    [string]$Serialnumber,

    [Parameter(Mandatory = $false)]
    [switch]$Brief,

    [Parameter(Mandatory = $false)][ValidateSet('Warranty', 'ProductSpecification')]
    [string]$Type = 'Warranty',

    [Parameter(Mandatory = $false)]
    [switch]$ForceWeb,

    [Parameter(Mandatory = $false)][ValidateSet('Warranty', 'ProductSpecification', 'ProductID')]
    [string]$InspectCache,

    [Parameter(Mandatory = $false)][ValidateSet('All','Warranty', 'ProductSpecification', 'ProductID')]
    [string]$ClearCache,

    [Parameter(Mandatory = $false)]
    [Switch]$ShowCachesCombined,

    [Parameter(Mandatory = $false)]
    [Switch]$VerboseLogging
)
begin {
    if ($Brief) {
        $Properties = @(
            @{Expression={$_.DeliveryType -replace 'depot', 'Depot/mail' -replace 'on_site', 'On site'};Label='DeliveryType'},
            @{Expression={$_.ProductName -replace "\(.+$"};Label='ProductName'},
            @{Expression={$_.Model};Label='Model'},
            @{Expression={$_.SerialNumber};Label='SerialNumber'},
            @{Expression={$_.Name};Label='Name'},
            @{Expression={$_.Status -replace '-1', 'Expired' -replace '1', 'Active'};Label='Status'},     
            @{Expression={((Get-Date $_.End) - (Get-date)).TotalDays -replace "(,|\.).+$"};Label='DaysLeft'},
            @{Expression={$_.YearsSinceBought};Label='YearsSinceBought'}
        )
    }
    else {
        $Properties = @(
            @{Expression={$_.WarrentyType};Label='WarrentyType'},
            @{Expression={$_.DeliveryType -replace 'depot', 'Depot/mail' -replace 'on_site', 'On site'};Label='DeliveryType'},
            @{Expression={$_.ProductName};Label='ProductName'},
            @{Expression={$_.Model};Label='Model'},
            @{Expression={$_.SerialNumber};Label='SerialNumber'},
            @{Expression={$_.Name};Label='Name'},
            @{Expression={$_.Description};Label='Description'},
            @{Expression={$_.Status -replace '-1', 'Expired' -replace '1', 'Active'};Label='Status'},
            @{Expression={$_.Start};Label='Start'},
            @{Expression={$_.End};Label='End'},
            @{Expression={$_.Duration};Label='Duration'},
            @{Expression={((Get-Date $_.End) - (Get-date)).TotalDays -replace "(,|\.).+$"};Label='DaysLeft'},
            @{Expression={$_.Origin};Label='Origin'},
            @{Expression={$_.CountryName};Label='CountryName'}
            @{Expression={$_.YearsSinceBought};Label='YearsSinceBought'}
        )
    }

    $Script:CacheProductID = @()
    $Script:CacheWarranty = @()    
    $Script:CacheProductSpecification = @()

    $Script:ProductIDResults = @()
    $Script:WarrantyResults = @()
    $Script:SpecResults = @()

    $Script:CacheFileProductID = "$PSScriptRoot\Get-LenovoInfo-ProductIDCache.csv"
    $Script:CacheFileWarranty = "$PSScriptRoot\Get-LenovoInfo-WarrantyCache.csv"
    $Script:CacheFileProductSpecifications = "$PSScriptRoot\Get-LenovoInfo-ProductSpecificationsCache.csv"

    Function Write-Log {
        param(
            [ValidateSet(0, 1, 2, 3, 4)]
            [int]$Level,

            [Parameter(Mandatory=$true)]
            [string]$Message            
        )
        $Message = $Message.Replace("`r",'').Replace("`n",' ')
        switch ($Level) {
            0 { $Status = 'Info'    ;$FGColor = 'White'   }
            1 { $Status = 'Success' ;$FGColor = 'Green'   }
            2 { $Status = 'Warning' ;$FGColor = 'Yellow'  }
            3 { $Status = 'Error'   ;$FGColor = 'Red'     }
            4 { $Status = 'Console' ;$FGColor = 'Gray'    }
            Default { $Status = ''  ;$FGColor = 'Black'   }
        }
        if ($VerboseLogging) {
            Write-Host "$((Get-Date).ToString()) " -ForegroundColor 'DarkGray' -NoNewline
            Write-Host "$Status" -ForegroundColor $FGColor -NoNewline

            if ($level -eq 4) {
                Write-Host ("`t " + $Message) -ForegroundColor 'Cyan'
            }
            else {
                Write-Host ("`t " + $Message) -ForegroundColor 'White'
            }
        }
        if ($Level -eq 3) {
            $LogErrors += $Message
        }
    }
    Function Invoke-ProductIDWebRequest {
        Param(
            [String]$Serialnumber
        )
        try {
            Clear-Variable Url -ErrorAction SilentlyContinue            
            $Url = "https://pcsupport.lenovo.com/gb/en/api/v4/mse/getproducts?productId=$SerialNumber" #CASE-SENSITIVE!

            Clear-Variable Response -ErrorAction SilentlyContinue
            $Response = Invoke-WebRequest -Uri $Url -ErrorAction Stop

            Clear-Variable Product -Scope script -ErrorAction SilentlyContinue
            $Script:ProductWebResponse = $Response.Content | ConvertFrom-Json

            Write-Log -Level 1 -Message "Invoke-WebRequest - ProductID: $($Response.StatusCode) $($Response.StatusDescription) '$Url'"

            if (!$Script:ProductWebResponse.id) {
                Write-Log -Level 2 -Message "Invoke-WebRequest - ProductID: Incomplete response. Product ID not found for serialnumber '$Serialnumber'"
            }
            else {
                $Product = [pscustomobject]@{
                    SerialNumber    = $SerialNumber
                    ProductID       = $Script:ProductWebResponse.Id
                    ModelCode       = ($Script:ProductWebResponse.Id -split '/')[-2]
                }
                $Script:CacheProductID += $Product
                $Script:CacheResultProductID = $Product

                try {
                    if (!$ForceWeb) {
                        $Product | Export-Csv -Path $Script:CacheFileProductID -Delimiter ';' -Encoding utf8 -Append -Force
                        Write-Log -Level 1 -Message "CSV Cache Product ID - Product ID for serialnumber '$Serialnumber' added to cache"
                    }
                }
                catch {
                    Write-Log -Level 3 -Message "CSV Cache Product ID - Failed to add product ID for serialnumber '$Serialnumber' to cache $($_.Exception.Message)"
                }
            }
        }
        catch {
            Write-Log -Level 3 -Message "Invoke-WebRequest - ProductID: $($Response.StatusCode) $($Response.StatusDescription) '$Url', Error: $($_.Exception.Message)"
        }
    }
    Function Invoke-ProductSpecificationWebRequest {
        param(
            [String]$ModelCode,
            [String]$Serialnumber
        )
        try {
            Clear-Variable Url -ErrorAction SilentlyContinue
            $Url = "https://psref.lenovo.com/api/model/Info/SpecData?model_code=$ModelCode"

            Clear-Variable Response -ErrorAction SilentlyContinue
            $Response = Invoke-WebRequest -Uri $Url -ErrorAction Stop
            
            Clear-Variable Specs -ErrorAction SilentlyContinue
            $Specs = $Response | ConvertFrom-Json

            Write-Log -Level 1 -Message "Invoke-WebRequest - Product Specification: $($Response.StatusCode) $($Response.StatusDescription) '$Url'"
        }
        catch {
            Write-Log -Level 3 -Message "Invoke-WebRequest - Product Specification: $($Response.StatusCode) $($Response.StatusDescription) '$Url', Error: $($_.Exception.Message)"
        }

        if ($Specs.data) {
            # Loop all components

            $Parts = @()
            $Specs.data.SpecData | Select-Object Title, Name, Content | ForEach-Object {                
                $Part = [pscustomobject]@{
                    Serial          = $Serialnumber
                    ModelCode       = $ModelCode                    
                    Part            = $_.Name
                    Value           = ($_ | Select-Object -ExpandProperty content) -replace "(&reg;|&trade;)" -replace '\<.+>' -replace "(\r\n|\n|\r)", "" -replace '(USB-C).+$', 'USB-C'
                }
                $Parts += $Part
            }

            # Add each component to the script result variable
            $Script:SpecResults += $Parts

            # Update imported cache with new live-entries, discarding serialnumber...
            $Script:CacheProductSpecification += $Parts | Select-Object ModelCode, Part, Value

            try {
                if (!$ForceWeb) {
                    # Export the results to offline cache file, discarding serialnumber
                    $Parts | Select-Object ModelCode, Part, Value | Export-Csv -Path $Script:CacheFileProductSpecifications -Delimiter ';' -Encoding utf8 -Append -Force                    
                    Write-Log -Level 1 -Message "Offline Cache - Product Specifications: Model '$ModelCode' added to cache"
                }
            }
            catch {
                Write-Log -Level 3 -Message "Offline Cache - Product Specifications: Failed to add model '$ModelCode' to cache $($_.Exception.Message)"
            }
        }        
        else {
            Write-Log -Level 2 -Message "Invoke-WebRequest - Product Specification: Incomplete response. Product specification not found for model code '$ModelCode'"
        }
        
    }
    Function Invoke-WarrantyWebRequest {
        Param(
            [String]$Serialnumber,
            [String]$ProductID
        )
        if ($ProductID) {
            try {
                Clear-Variable Url -ErrorAction SilentlyContinue
                $Url = 'https://pcsupport.lenovo.com/us/en/products/' + $ProductID.ToLower() + '/warranty'

                Clear-Variable Result -ErrorAction SilentlyContinue
                $Result = Invoke-WebRequest -uri $Url -ErrorAction Stop

                Write-Log -Level 1 -Message "Invoke-WebRequest - Warranty: $($Result.StatusCode) $($Result.StatusDescription) '$Url'"
            }
            catch {
                Write-Log -Level 3 -Message "Invoke-WebRequest - Warranty: $($Result.StatusCode) $($Result.StatusDescription) '$Url', Error: $($_.Exception.Message)"
            }
        }

        # Parse HTML and extract JSON
        if ($Result) {
            Clear-Variable PSObject -ErrorAction SilentlyContinue
            $PSObject = ((($Result.content -split "`n") | Where-Object {$_ -match 'var ds_warranties'}) -replace 'var ds_warranties = window.ds_warranties \|\| ' -replace ';$') | ConvertFrom-Json
        }

        #Uncomment to inspect RAW warranty webresults (use -Forceweb)
        #$PSObject;Break

        if ($PSObject) {

            $Warranties = @()
            $Warranties += $PSObject.ContractWarranties | Where-Object {$_.Status -eq 'Active'}
            $Warranties += $PSObject.BaseWarranties
            $Warranties += $PSObject.UpmaWarranties
            $Warranties += $PSObject.AodWarranties
            $Warranties += $PSObject.InstantWarranties
            $Warranties += $PSObject.SaeWarranties

            $Warranties | ForEach-Object {
                Clear-Variable Warranty -ErrorAction SilentlyContinue
                $Warranty = $_
                $ApproximateAgeInYears = (((Get-Date) - (Get-Date $Warranty.Start)).TotalDays / 365)
                try {                    
                    Clear-Variable WarrentyType, YearsSinceBought, CountryName, Description -ErrorAction SilentlyContinue
                    if ($Warranty.ChargeCode) {
                        $WarrentyType        = $Warranty.ChargeCode
                        $YearsSinceBought    = ''
                    }
                    else {
                        $WarrentyType        = $Warranty.WarrentyType
                        $YearsSinceBought    = [math]::Round($ApproximateAgeInYears,2)
                    }

                    if ($Warranty.AssetLocation) {
                        $CountryName        = $Warranty.AssetLocation
                    }
                    else {
                        $CountryName        = $Warranty.CountryName
                    }

                    if ($Warranty.SLA) {
                        $Description        = $Warranty.SLA
                    }
                    else {
                        $Description        = $Warranty.Description
                    }

                    $Obj = [pscustomobject]@{
                        WarrentyType        = $WarrentyType                 
                        DeliveryType        = $Warranty.DeliveryType
                        ProductName         = $PSObject.ProductName
                        Model               = ($PSObject.MachineType, $PSObject.Mode -join '')
                        SerialNumber        = $PSObject.Serial
                        Name                = $Warranty.Name
                        Description         = $Description
                        Status              = $Warranty.Status
                        Start               = $Warranty.Start
                        End                 = $Warranty.End
                        Duration            = $Warranty.Duration
                        Origin              = $Warranty.Origin
                        CountryName         = $CountryName
                        YearsSinceBought    = $YearsSinceBought
                    }
                    $Script:WarrantyResults += $Obj
                    $Script:CacheWarranty += $Obj
                    
                    if (!$ForceWeb) {
                        $Obj | Export-Csv -Delimiter ';' -Path $Script:CacheFileWarranty -Append -Encoding utf8 -Force -ErrorAction Stop
                        Write-Log -Level 1 -Message "Offline Cache - Warranty $($Warranty.Name) for serialnumber '$Serialnumber' added to cache"
                    }
                }
                catch {
                    Write-Log -Level 3 -Message "Offline Cache - Failed to add Warranty $($Warranty.Name) for serialnumber '$Serialnumber' to cache $($_.Exception.Message)"
                }
            }
        }
        else {
            Write-Log -Level 3 -Message "Invoke-WebRequest - Warranty: Failed for serialnumber '$Serialnumber'"
        }
    }
     Function Import-CSVCache {
        Param([string]$File)
        try {
            if ($File -match 'WarrantyCache') {
                $Script:CacheWarranty += Import-Csv $File -Delimiter ';' -Encoding utf8
                Write-Log -Level 1 -Message "Offline Cache - File loaded ($($Script:CacheWarranty.count) entries)"
            }
            elseif ($File -match 'ProductSpecificationsCache') {
                $Script:CacheProductSpecification += Import-Csv $File -Delimiter ';' -Encoding utf8
                Write-Log -Level 1 -Message "Offline Cache - File loaded ($($Script:CacheProductSpecification.count) entries)"
            }
            elseif ($File -match 'ProductID') {                
                $Script:CacheProductID += Import-Csv $File -Delimiter ';' -Encoding utf8
                Write-Log -Level 1 -Message "Offline Cache - File loaded ($($Script:CacheProductID.count) entries)"
            }
        }
        catch {
            Write-Log -Level 3 -Message "Offline Cache - Failed to load file $($_.Exception.Message)"
        }
    }
    function Clear-CSVCache {
        Param($File)
        try {
            Remove-Item $File -Force -ErrorAction Stop
            Write-Log -Level 1 -Message "Remove-Item - File '$File' was deleted"
        }
        catch {
            if ($_.Exception.Message -match 'not exist') {
                Write-Log -Level 2 -Message "Remove-Item - Cache file did not exist '$File'"
            }
            else {
                Write-Log -Level 3 -Message "Remove-Item - Failed to delete file '$File' $($_.Exception.Message)"
            }
        }
    }
    function Test-CSVCache {
        Param($File)
        if (Test-Path $File) {
            Write-Log -Level 0 -Message "Offline Cache - File exist ('$File')"
            Import-CSVCache -File $File
        }
        else {
            Write-Log -Level 0 -Message "Offline Cache - File not found ('$File')"
        }
    }
    Function Test-CSVProductIDCacheMatch {
        Param(
            [String]$Serialnumber
        )
        if ($Script:CacheProductID -and !$ForceWeb) {
            Clear-Variable CacheResultProductID -Scope Script -ErrorAction SilentlyContinue
            $Script:CacheResultProductID = $Script:CacheProductID | Where-Object {$_.SerialNumber -eq $Serialnumber}

            if ($Script:CacheResultProductID) {
                Write-Log -Level 0 -Message "Offline Cache - Product ID: Serialnumber '$Serialnumber' found in cache"
            }
            else {
                Write-Log -Level 0 -Message "Offline Cache - Product ID: Serialnumber '$Serialnumber' not found in cache"
            }
        }
    }
    Function Test-CSVProductSpecificationCacheMatch {
        Param(
            [String]$ModelCode,
            [String]$Serialnumber
        )
        if ($Script:CacheProductSpecification) {
            Clear-Variable CacheResultsSpecification -Scope Script -ErrorAction SilentlyContinue
            $Script:CacheResultsSpecification = $Script:CacheProductSpecification | Where-Object {$_.ModelCode -eq $ModelCode}

            if ($CacheResultsSpecification -and !$ForceWeb) {
                $Script:CacheResultsSpecification | Add-Member -MemberType NoteProperty -Name Serial -Value $Serialnumber -Force
                $Script:SpecResults += $CacheResultsSpecification
                Write-Log -Level 0 -Message "Offline Cache - Product Specifications: Model '$ModelCode' found in cache"
            }
            else {
                Write-Log -Level 0 -Message "Offline Cache - Product Specifications: Model '$ModelCode' not found in cache"
            }
        }
    }
    Function Test-CSVWarrantyCacheMatch {
        Param([String]$Serialnumber)
        Clear-Variable CacheResultsWarranty -Scope Script -ErrorAction SilentlyContinue
        $Script:CacheResultsWarranty = $Script:CacheWarranty | Where-Object {$_.SerialNumber -eq $Serialnumber}
        if ($Script:CacheResultsWarranty -and !$ForceWeb) {
            $Script:WarrantyResults += $Script:CacheResultsWarranty
            Write-Log -Level 0 -Message "Offline Cache - Warranty: Serialnumber '$Serialnumber' found in cache"
        }
        else {
            Write-Log -Level 0 -Message "Offline Cache - Warranty: Serialnumber '$Serialnumber' not found in cache"
        }
    }

    # Load needed offline cache from CSV files if they exist
    Test-CSVCache -File $Script:CacheFileProductID
    if ($Type -eq 'Warranty' -or $InspectCache -eq 'Warranty' -or $ShowCachesCombined)               {Test-CSVCache -File $Script:CacheFileWarranty      }
    if ($Type -eq 'ProductSpecification' -or $InspectCache -eq 'ProductSpecification' -or $ShowCachesCombined)   {Test-CSVCache -File $Script:CacheFileProductSpecifications}

    # Load needed offline cache from CSV files if they exist
    if ($InspectCache) {
        Write-Log -Level 2 -Message "Parameter '-InspectCache' used. Displaying cache for '$InspectCache', ignoring other parameters"        
        if ($InspectCache -eq 'Warranty') {
            Return $Script:CacheWarranty
        }
        elseif ($InspectCache -eq 'ProductSpecification') {
            Return $Script:CacheProductSpecification
        }
        elseif ($InspectCache -eq 'ProductID') {
            Return $Script:CacheProductID
        }
        Break
    }

    if ($ShowCachesCombined) {
        $Script:Devices = @()
        $Script:CacheProductID | ForEach-Object {
            Clear-Variable SerialNumber, ModelCode -ErrorAction SilentlyContinue
            $Serialnumber = $_.SerialNumber
            $ModelCode = $_.ModelCode

            if ($Serialnumber -and $ModelCode) {

                Clear-Variable WarrantyMatch, ProductSpecificationMatch -ErrorAction SilentlyContinue            
                $WarrantyMatch = $Script:CacheWarranty | Where-Object {$_.SerialNumber -eq $Serialnumber -and $_.Name -notmatch 'Battery'} | Select-Object -Last 1                        
                $ProductSpecificationMatch = $Script:CacheProductSpecification | Where-Object {$_.ModelCode -match $ModelCode}

                if ($WarrantyMatch -and $ProductSpecificationMatch) {
                    Clear-Variable Device, DeviceAge -ErrorAction SilentlyContinue
                    $DeviceAge = (((Get-Date) - (Get-Date $WarrantyMatch.Start)).TotalDays / 365)

                    $Device = [pscustomobject]@{
                        Serial              = [string]$Serialnumber
                        ModelCode           = [string]$ModelCode
                        ProductName         = [string]$WarrantyMatch.ProductName -replace "\(.+$"
                        WarrantyName        = [string]$WarrantyMatch.Name
                        WarrantyDaysLeft    = [int](((Get-Date $WarrantyMatch.End) - (Get-date)).TotalDays -replace "(,|\.).+$")
                        DeviceAge           = [decimal]([math]::Round($DeviceAge,2))
                        DeviceMemory        = [string]($ProductSpecificationMatch | Where-Object {$_.Part -eq 'Memory'}          | Select-Object -ExpandProperty Value)
                        DeviceProcessor     = [string]($ProductSpecificationMatch | Where-Object {$_.Part -eq 'Processor'}       | Select-Object -ExpandProperty Value)
                        DeviceStorage       = [string]($ProductSpecificationMatch | Where-Object {$_.Part -eq 'Storage'}         | Select-Object -ExpandProperty Value)
                        DeviceSecurityChip  = [string]($ProductSpecificationMatch | Where-Object {$_.Part -eq 'Security Chip'}   | Select-Object -ExpandProperty Value)
                    }
                    $Script:Devices += $Device
                }
            }
        }        
    }

    # Delete offline CSV cache-file
    if($ClearCache) {
        Write-Log -Level 2 -Message "Parameter '-ClearCache' used. Deleting offline cache-file(s)"
        if ($ClearCache -match '(All|ProductID)')               {Clear-CSVCache -File $Script:CacheFileProductID}
        if ($ClearCache -match '(All|ProductSpecification)')    {Clear-CSVCache -File $Script:CacheFileProductSpecifications}
        if ($ClearCache -match '(All|Warranty)')                {Clear-CSVCache -File $Script:CacheFileWarranty}
    }

    if ($ForceWeb) {
        Write-Log -Level 2 -Message "Parameter '-ForceWeb' used. Cache is excluded (not used, and results are not added to cache)"
    }
}
process {
    if (!$ShowCachesCombined -and !$InspectCache) {
        if ($Serialnumber) {
            Test-CSVProductIDCacheMatch -Serialnumber $Serialnumber

            if (!$Script:CacheResultProductID -or $ForceWeb) {
                Invoke-ProductIDWebRequest -Serialnumber $Serialnumber
            }

            if ($Script:CacheResultProductID) {

                if ($Type -eq 'ProductSpecification') {
                    Test-CSVProductSpecificationCacheMatch -ModelCode $Script:CacheResultProductID.ModelCode -Serialnumber $Serialnumber
                    if (!$Script:CacheResultsSpecification -or $ForceWeb) {
                        Invoke-ProductSpecificationWebRequest -ModelCode $Script:CacheResultProductID.ModelCode -Serialnumber $Serialnumber
                    }
                }

                if ($Type -match 'Warranty') {
                    Test-CSVWarrantyCacheMatch -Serialnumber $Serialnumber
                    if (!$Script:CacheResultsWarranty -or $ForceWeb) {
                        Invoke-WarrantyWebRequest -Serialnumber $Serialnumber -ProductID $Script:CacheResultProductID.ProductID
                    }
                }

            }
        }
        else {
            Write-Log -Level 2 -Message 'No input provided. Please input one or more serialnumbers'
        }
    }
}
end {
    if (!$ShowCachesCombined -and !$InspectCache) {         
        if ($Type -eq 'Warranty') {
            if ($Brief) {
                if ($Script:WarrantyResults.Name -match 'Premier') {
                    $Output = $Script:WarrantyResults | Where-Object {$_.Name -match 'Premier'} | Select-Object $Properties
                }
                else {
                    $Output = $Script:WarrantyResults | Where-Object {$_.Name -notmatch 'Battery'} | Select-Object $Properties
                }
            }
            else {
                $Output = $Script:WarrantyResults | Select-Object $Properties
            }
        }
        elseif ($Type -eq 'ProductSpecification') {        
            if ($Brief) {
                $Output = $Script:SpecResults | Where-Object {$_.Part -match "(Processor|Graphics|Memory|Storage|Display|WLAN)"} | Select-Object ModelCode, Part, Value
            }
            else {
                $Output = $Script:SpecResults
            }
        }
        Return $Output
    }
    else {        
        Return $Script:Devices
    }
}