<# .NOTES Written by Ian Cammarata http://ian.cammarata.us Get-HpProductInfo.ps1 v0.10 This will probably only work for products sold in the U.S. unless you change the country codes in $hpWarrantyQueryURL and the $htClient.Headers.set(...Cookie... To Do: -Multi-threading the http downloads and multiple retry attemps (partsufer is so unreliable lately) -Generate a proper object (this will allow tab completion and easier programmatic use) SNs for testing: CND120CJH0, CNU305BP9N, CNFKF52058, JPBCD3R1H3, CNU349BDTD .SYNOPSIS Query by SN to retreive HP product info from PartSufer and HP Business warranty lookup. .EXAMPLE PS> Get-HpProductInfo JPBCD3R1H3 #> 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 IF ( -not ( Test-Path Variable:\\fullRegionName ) ) { #This prevents errors from being generated when running from ISE Set-Variable fullRegionName -Option Constant -value "United States" Set-Variable abbrRegionName -Option Constant -Value "US" Set-Variable regionSuffix -Option Constant -Value "#ABA" Set-Variable ForeignPartSuffixes -Option Constant -Value "-031|-041|-051|-061|-071|-081|-091|-131|-141|-161|-171|-201|-211|-251|-261|-271|-281|-291|-A41|-AB1|-AD1|-B31|-B71|-BA1|-BB1|-BG1|-D61|-DB1|-DD1|-DJ1|-FL1" Set-Variable OtherPartRejectionStrings -Option Constant -value "no longer supplied|Not Available|Hebrew|Cyrillic|Greek|Arabic|Bulk|220V|- AR|- CS|- DA|- NL|- FI|- DE|- EL|- HE|- HU|- KO|- NO|- PL|- ZHCN|- ZHTW|- TH|PCID|SWID| CH | CH<|CHINARUSS" Set-Variable productObjectVersion -option Constant -value 0.4 Set-Variable partSurferQueryUrl -option Constant -value "http://partsurfer.hp.com/Search.aspx?searchText=" Set-Variable hpWarrantyBaseUrl -option Constant -value "https://h20565.www2.hp.com" Set-Variable hpWarrantyStartPath -option Constant -value "/portal/site/hpsc/public/wc/home/" Set-Variable hpWarrantyQueryUrl -option Constant -value "https://h20566.www2.hp.com/portal/site/hpsc/template.PAGE/action.process/public/wc/home/?javax.portlet.action=true&javax.portlet.sync=d549c6d9a9acda7f8405adb432a15c01&javax.portlet.tpst=c4efedb99acca32ea782977bb053ce01&javax.portlet.prp_c4efedb99acca32ea782977bb053ce01=wsrp-interactionState%3Daction%253Dfind&javax.portlet.begCacheTok=com.vignette.cachetoken&javax.portlet.endCacheTok=com.vignette.cachetoken" Set-Variable cacheDir -option Constant -value (Join-Path $env:APPDATA "HpProductInfo_Cache") Set-Variable maxCacheAge -option Constant -value 90 #In Days } ###### Prep IF ( !( Test-Path $cacheDir ) ){ $null = New-Item -Type "Directory" -Path $cacheDir } ### This was causing issues with other PowerShell scripts. It will remain disabled till I learn to fix it across all versions of PowerShell or I no longer need to support PS 1.0. <# ###### Default display property set #Maybe this will be added back in future, not really necessary anyway $objectTemplateFile = Join-Path $cacheDir "HpProductObjectTemplate_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 "(\r|\f|\n)*" -replace "\t", " " -replace " " -replace "> <", "><" -replace " " -replace "" -replace "" } 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 "Load initial warranty lookup page to get ID cookies, run ahead of time for the session to settle on the server or w/e" $cookieContainer = New-Object System.Net.CookieContainer [net.httpWebRequest] $webReq = [net.webRequest]::create("$($hpWarrantyBaseURL)$($hpWarrantyStartPath)") $webReq.CookieContainer = $cookieContainer [net.httpWebResponse] $webResp = $webReq.GetResponse() $strRdr = New-Object IO.StreamReader($webResp.GetResponseStream()) $htData = $strRdr.ReadToEnd() $webResp.Close() Write-Debug "Parse out POST action" $null = $htData -match "
" -inputObject $htData -AllMatches $postInputs.Matches | ? { $_ -notmatch ".*(submit|image|radio).*" } | % { $null = $_ -match ".*?name=`"(.*?)`".*?>" $newData = "$($Matches[1])=" IF ( $_ -match ".*?value=`"(.*?)`".*?>" ) { Write-Debug "$($newData) `"$( IF ( $Matches[1].Length -gt 65 ) { "$($Matches[1].Substring(0,55))...(truncated)" } ELSE { $Matches[1] } )`"" $newData += $Matches[1] } $postData += "$($newData)&" } $postData += "ctl00`$BodyContentPlaceHolder`$radProd=$($fullSku)" $postData += "&ctl00`$BodyContentPlaceHolder`$btnProdSubmit= View Selected Product Details " $postData += "&ctl00`$BodyContentPlaceHolder`$ddlCountry=$($abbrRegionName)" Write-Debug "Post header complete. Submitting..." $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 = $strRdr.ReadToEnd() $webResp.Close() $htData = CleanHtml( $htData ) IF ( $Script:PSBoundParameters.ContainsKey("Debug") ) { Write-Debug "Raw HTML written to cache" Set-Content (Join-Path $cacheDir "$($SerialNumber).raw.html") $htData } } WHILE ( $htData -match 'type="radio"' ) } ELSE { Write-Error "Multiple serial# matches, and we weren't able to determine the correct one." } } Write-Debug "Create psObject representing the product" $hpProduct = New-Object psObject Write-Debug "Load generic sku data into the object" Write-Debug "Load SerialNumber" $hpProduct | Add-Member NoteProperty SerialNumber $SerialNumber Write-Debug "Load ProductNumber" $null = $htData -match "(.*?)" $hpProduct | Add-Member NoteProperty ProductNumber $Matches[1] Write-Debug "Load Description" $null = $htData -match "(.*?)" $hpProduct | Add-Member NoteProperty Description $Matches[1] $ErrorActionPreference = "Continue" Write-Debug "/!\End critical section/!\" Write-Debug "Add Spare and OEM parts to psObject (Check boxes are no longer supplied by the site to determine part availability)" $hpProduct | Add-Member NoteProperty OemParts (New-Object psObject) $hpProduct | Add-Member NoteProperty Spares (New-Object psObject) $trMatches = Select-String ".*?" -inputObject $htData -AllMatches foreach ( $trMatch in $trMatches.Matches ){ IF ( $trMatch.Value -notmatch ".*($($ForeignPartSuffixes)|$($OtherPartRejectionStrings)).*" ) { $spanMatches = Select-String "(.*?)" -inputObject $trMatch.Value -AllMatches $partObject = New-Object psObject $partObject | Add-Member NoteProperty PartNumber $spanMatches.Matches[0].Groups[1].Value $partObject | Add-Member NoteProperty Description $spanMatches.Matches[1].Groups[1].Value $BomType = "OemParts" IF ( $trMatch.Value -match ".*_gridSpareBOM_.*" ) { $BomType = "Spares" } $ErrorActionPreference = "SilentlyContinue" $hpProduct.$BomType | Add-Member NoteProperty $partObject.PartNumber $partObject $ErrorActionPreference = "Continue" Write-Debug "$($BomType): $($partObject.PartNumber) - $($partObject.Description)" } ELSE { Write-Debug "Discarding invalid or no longer supplied part." } } $warrAttempts = 0 $trMatches = "" DO { sleep ( 10 + $warrAttempts * 15 ) Write-Debug "Create POST request to retrieve warranty data. Attempt #$($warrAttempts)" [net.httpWebRequest] $webReq = [net.webRequest]::create("$($hpWarrantyBaseURL)$($hpWarrantyQueryPath)") $webReq.CookieContainer = $cookieContainer $webReq.Method = "POST" $postData = "rows[0].item.serialNumber=$($SerialNumber)&rows[0].item.countryCode=$($abbrRegionName)&rows[1].item.serialNumber=&rows[1].item.countryCode=US&rows[2].item.serialNumber=&rows[2].item.countryCode=US&rows[3].item.serialNumber=&rows[3].item.countryCode=US&rows[4].item.serialNumber=&rows[4].item.countryCode=US&rows[5].item.serialNumber=&rows[5].item.countryCode=US&rows[6].item.serialNumber=&rows[6].item.countryCode=US&rows[7].item.serialNumber=&rows[7].item.countryCode=US&rows[8].item.serialNumber=&rows[8].item.countryCode=US&rows[9].item.serialNumber=&rows[9].item.countryCode=US&submitButton=Submit" $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 = $strRdr.ReadToEnd() $webResp.Close() $htData = CleanHtml( $htData ) $warrAttempts++ $trMatches = Select-String "" -inputObject $htData -AllMatches } WHILE ( $warrAttempts -lt 6 -and -not $trMatches ) Write-Debug "Parse warranty data and add to the object" $hpProduct | Add-Member NoteProperty Warranties (New-Object psObject) FOREACH ( $trMatch in $trMatches.Matches ) { $tdMatches = Select-String "" -inputObject $trMatch -AllMatches $warrData = $tdMatches.Matches | % { $_.value -replace "]*>" -replace "Wty: " -replace "HP " -replace "HW Maintenance ", "HWM " -replace "Support ?(for )?" } IF ( $warrData[1].Length -or $warrData[2].Length ) { $warrObject = New-Object psObject $warrObject | Add-Member NoteProperty Start ( '{0:yyyy-MM-dd}' -f ( Get-Date $warrData[1] ) ) $warrObject | Add-Member NoteProperty End ( '{0:yyyy-MM-dd}' -f ( Get-Date $warrData[2] ) ) $hpProduct.Warranties | Add-Member NoteProperty $warrData[0] $warrObject Write-Debug "Added Warranty: $($warrData[0]) - $( '{0:yyyy-MM-dd}' -f ( Get-Date $warrData[1] ) ) to $( '{0:yyyy-MM-dd}' -f ( Get-Date $warrData[2] ) )" } ELSE { $hpProduct.Warranties | Add-Member NoteProperty $warrData[0] "" } } $hpProduct | Add-Member NoteProperty __QueryDate (Get-Date) $hpProduct | Add-Member NoteProperty __productObjectVersion $productObjectVersion $hpProduct | Export-Clixml (Join-Path $cacheDir "$($hpProduct.SerialNumber)_$($hpProduct.ProductNumber)_v$($productObjectVersion).xml") } #Import line is potentially error prone, make it more specific later on IF ( -not $useCache ) { downloadProductData } $hpProduct = Import-Clixml ( Join-Path $cacheDir "$($SerialNumber)_*" ) IF ( $OutputObject ) { $hpProduct } ELSE { $lineLen = 75 "█" * $lineLen "Serial Number:`t$($hpProduct.SerialNumber)" "Product Number:`t$($hpProduct.ProductNumber -replace '#ABA' )" "Description:`t$($hpProduct.Description)" "" $partsTxt = @() $warranties = $hpProduct.Warranties | get-member -MemberType "NoteProperty" foreach ( $warranty in $warranties | % { $_.Name } ) { IF ( ($hpProduct.Warranties.$warranty | Get-Member | % { $_.Name } ) -contains "Start" ) { $partsTxt += "$($warranty) `t$($hpProduct.Warranties.$warranty.Start)`t$($hpProduct.Warranties.$warranty.End)" } ELSE { $partsTxt += $warranty } } "Warranties: $($partsTxt.Length)" "─" * $lineLen $partsTxt "" $parts = $hpProduct.OemParts | get-member -MemberType "NoteProperty" $partsTxt = @() foreach ( $part in $parts | % { $_.Name } ) { IF ( $PartQuery ) { IF ( $hpProduct.OemParts.$part.Description -match ".*$($PartQuery).*" ) { $partsTxt += "$($hpProduct.OemParts.$part.PartNumber) - $($hpProduct.OemParts.$part.Description)" } } ELSE { $partsTxt += "$($hpProduct.OemParts.$part.PartNumber) - $($hpProduct.OemParts.$part.Description)" } } "OEM Parts$(IF ( $PartQuery ) {" matching query '$($PartQuery)'"}): $($partsTxt.Length)" "─" * $lineLen $partsTxt "" $parts = $hpProduct.Spares | get-member -MemberType "NoteProperty" $partsTxt = @() foreach ( $part in $parts | % { $_.Name } ) { IF ( $PartQuery ) { IF ( $hpProduct.Spares.$part.Description -match ".*$($PartQuery).*" ) { $partsTxt += "$($hpProduct.Spares.$part.PartNumber) - $($hpProduct.Spares.$part.Description)" } } ELSE { $partsTxt += "$($hpProduct.Spares.$part.PartNumber) - $($hpProduct.Spares.$part.Description)" } } "Spares$(IF ( $PartQuery ) {" matching query '$($PartQuery)'"}): $($partsTxt.Length)" "─" * $lineLen $partsTxt "█"*$lineLen } } }