#
.NOTES
Written by Ian Cammarata http://ian.cammarata.us
Get-LenovoProductInfo.ps1 v0.2
Will this only work for products sold in the U.S.?
To Do:
-Generate a proper object (this will allow tab completion and easier programmatic use)
SNs for testing:
LRNMV31 | 0301-24U | ThinkPad Edge 15
.SYNOPSIS
Query by SN to retreive Lenovo warranty, product name and type, and parts.
.EXAMPLE
PS> Get-LenovoProductInfo MJDGZE4
#>
param(
#The serial number to query.
[Parameter( Mandatory = $True, Position = 0, ValueFromPipeline= $True )]
[Alias( "SN" )]
[String[]]
$SerialNumber,
#Search string for parts listing.
[Parameter( Position = 1 )]
[Alias( "Part" )]
$PartQuery,
#Output an object instead of an plain text.
[Switch]
[Alias( "Object" )]
$OutputObject,
#Purge cash for supplied SN(s) and fetch fresh data
[Switch]
[Alias( "Purge" )]
$Refresh
)
BEGIN {
Set-StrictMode -Version Latest
if ( $Script:PSBoundParameters.ContainsKey("Debug") ) { $DebugPreference = "Continue" } ### For some reason I was getting first 3 debug lines output in the Process block even with using -Debug, but none afterwards
###### Set Constants
#<# ### uncomment this line for testing in ISE after initial run
Set-Variable productObjectVersion -option Constant -value 0.4
Set-Variable lenovoWarrantyUrl -option Constant -value "https://csp.lenovo.com/ibapp/il/WarrantyStatus.jsp"
Set-Variable lenovoProductDescriptionUrl -option Constant -value "http://support.lenovo.com/en_US/guides-and-manuals/default.page"
Set-Variable lenovoPartsUrl -option Constant -value "http://support.lenovo.com/templatedata/Web%20Content/JSP/partsLookup.jsp"
Set-Variable cacheDir -option Constant -value (Join-Path $env:APPDATA "LenovoProductInfo_Cache")
Set-Variable maxCacheAge -option Constant -value 90 #In Days
#>
###### Prep
if ( !( Test-Path $cacheDir ) ){ $null = New-Item -Type "Directory" -Path $cacheDir }
###### Default display property set
$objectTemplateFile = Join-Path $cacheDir "LenovoProductObjectTemplate_v$($productObjectVersion).ps1xml"
if ( !( Test-Path $objectTemplateFile ) ) {
$template = ""
$template += "System.Management.Automation.PSCustomObject"
$template += "PSStandardMembersDefaultDisplayPropertySet"
$template += "ProductNumber"
$template += "SerialNumber"
$template += "Description"
$template += "WarrantyOnsite"
$template += "Parts"
$template += ""
Out-File $objectTemplateFile -Encoding "UTF8" -InputObject $template -Force
}
if ( $PSVersionTable.PSVersion.Major -gt 2 ) {
Update-TypeData $objectTemplateFile
}
elseif ( ! ( $host.Runspace.RunspaceConfiguration.Types | ? { $_.FileName -eq $objectTemplateFile } ) ) {
Update-TypeData $objectTemplateFile
}
}
PROCESS {
Function CleanHtml($htData) {
Return $htData -replace " ", "" -replace "(\r|\f|\n)*", "" -replace "\t", " " -replace " ", "" -replace "> <", "><" -replace "]*>", "" -replace "?b>", ""
}
Foreach ($SerialNumber in $SerialNumber) {
$SerialNumber = $SerialNumber.Trim().ToUpper()
Write-Debug ">> Check cache for XML copy of data"
$useCache = $false
if ( $cacheHit = ls $cacheDir | ? { $_.Name -like "$($SerialNumber)_*" } ) {
Write-Debug "Cache Hit!!! ($cacheHit)"
$useCache = $true
if ( $Refresh ) {
$useCache = $false
}
elseif ( $cacheHit -notlike "*_v$($productObjectVersion).xml" ) {
Write-Debug "Version Mismatch; Deleting... :-("
$useCache = $false
}
elseif ( ( Get-Date ) -gt $cacheHit.CreationTime.AddDays(90) ) {
Write-Debug "Stale Cache; Deleting... :-("
$useCache = $false
}
if ( ! $useCache ) { Remove-Item ( Join-Path $cacheDir $cacheHit ) }
}
Write-Debug "Using Cache: $($useCache)"
Write-Debug "<< End Check Cache"
function downloadProductData () {
Write-Debug "/!\Begin critical section/!\ Any errors here break the entire script."
$ErrorActionPreference = "Stop"
Write-Debug "Set up httpWebRequest for Warranty lookup"
[net.httpWebRequest] $webReq = [net.webRequest]::create($lenovoWarrantyUrl)
$webReq.Method = "POST"
$postData = "type=&serial=$($SerialNumber)&selLanguage=EN&country=897&iws=off&sitestyle=lenovo"
$postBuffer = [Text.Encoding]::Ascii.GetBytes($postData)
$webReq.ContentLength = $postBuffer.Length
$webReq.ContentType = "application/x-www-form-urlencoded"
$reqStr = $webReq.GetRequestStream()
$reqStr.Write( $postBuffer, 0, $postBuffer.Length)
$reqStr.Flush()
$reqStr.Close()
[net.httpWebResponse] $webResp = $webReq.GetResponse()
$strRdr = New-Object IO.StreamReader($webResp.GetResponseStream())
$htData = CleanHtml( $strRdr.ReadToEnd() )
$webResp.Close()
$warrantyHtData = $htData -replace ".*", "" -replace "Excluding:.*", "" -replace "End Date:", ""
$skuData = (Select-String "
]*>([^<:]+?) | " -InputObject $warrantyHtData -AllMatches).Matches | % {$_.Groups[1].Value}
Write-Debug "Create psObject representing the product"
$lenovoProduct = New-Object psObject
Write-Debug "Load generic sku data into the object"
$lenovoProduct | Add-Member NoteProperty ProductID $skuData[0]
$lenovoProduct | Add-Member NoteProperty Type ($skuData[1] -split "-")[0]
$lenovoProduct | Add-Member NoteProperty Model ($skuData[1] -split "-")[1]
$lenovoProduct | Add-Member NoteProperty SerialNumber $skuData[2]
$lenovoProduct | Add-Member NoteProperty Location $skuData[3]
$lenovoProduct | Add-Member NoteProperty WarrantyExpiration $skuData[4]
$ErrorActionPreference = "Continue"
Write-Debug "/!\End critical section/!\"
Write-Debug "Filter parts table rows out of the HTML."
$null = $htData -match ""
$trData = (Select-String "]*>[^<]* | ]*>([^-<]*) | ]*>([^<]*) |
" -InputObject $Matches[0] -AllMatches).Matches
Write-Debug "Add FRUs to the product object."
$lenovoProduct | Add-Member NoteProperty FRUs (New-Object psobject)
foreach ( $tr in $trData ){
$partObject = New-Object psObject
$partObject | Add-Member NoteProperty FRU $tr.Groups[1].Value
$partObject | Add-Member NoteProperty Description $tr.Groups[2].Value
$lenovoProduct.FRUs | Add-Member NoteProperty $partObject.FRU $partObject -ErrorAction "SilentlyContinue"
}
Write-Debug "Create WebClient to query ProductName"
$htClient = New-Object System.Net.Webclient
$htData = $htClient.DownloadString("http://support.lenovo.com/en_US/guides-and-manuals/default/1343112652708.ajax?method=getQuickPath&QuickPathSearch=$($lenovoProduct.ProductID)")
$null = $htData -match "(.*?) \(.*?"
$lenovoProduct | Add-Member NoteProperty ProductName $Matches[1]
$lenovoProduct | Add-Member NoteProperty __QueryDate (Get-Date)
$lenovoProduct | Add-Member NoteProperty __productObjectVersion $productObjectVersion
$lenovoProduct | Export-Clixml (Join-Path $cacheDir "$($lenovoProduct.SerialNumber)_$($lenovoProduct.ProductID)_v$($productObjectVersion).xml")
return $lenovoProduct
}
#Import line is potentially error prone, make it more specific later on
if ( $useCache ) { $lenovoProduct = Import-Clixml ( Join-Path $cacheDir "$($SerialNumber)_*" ) }
else { $lenovoProduct = downloadProductData }
if ( $OutputObject ) {
$lenovoProduct
}
Else {
$lineLen = 75
"█" * $lineLen
"Serial Number:`t$($lenovoProduct.SerialNumber)"
"Product ID:`t$($lenovoProduct.ProductID )"
"Product Name:`t$($lenovoProduct.ProductName)"
"Warranty End:`t$($lenovoProduct.WarrantyExpiration)"
""
$parts = $lenovoProduct.FRUs | get-member -MemberType "NoteProperty"
$partsTxt = @()
foreach ( $part in $parts | % { $_.Name } ) {
if ( $PartQuery ) {
if ( $lenovoProduct.FRUs.$part.Description -match ".*$($PartQuery).*" ) {
$partsTxt += "$($lenovoProduct.FRUs.$part.FRU) - $($lenovoProduct.FRUs.$part.Description)"
}
}
Else { $partsTxt += "$($lenovoProduct.FRUs.$part.FRU) - $($lenovoProduct.FRUs.$part.Description)" }
}
"FRUs$(if ( $PartQuery ) {" matching query '$($PartQuery)'"}): $($partsTxt.Length)"
"─" * $lineLen
$partsTxt
"█"*$lineLen
}
}
}