<# .NOTES Written by Ian Cammarata http://ian.cammarata.us Get-HpProductInfo.ps1 v0.6 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) SNs for testing: CND120CJH0, CNU305BP9N, CNFKF52058, JPBCD3R1H3 .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 #<# ### uncomment this line for testing in ISE after initial run 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 } ###### Default display property set $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 " ", "" } 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 "Create WebClient and cookie needed for PartSurfer" $htClient = New-Object System.Net.Webclient $htClient.Headers.Set([System.Net.HttpRequestHeader]::Cookie, "Country=United%20States") Write-Debug "/!\Begin critical section/!\ Any errors here break the entire script." $ErrorActionPreference = "Stop" if ( $Script:PSBoundParameters.ContainsKey("Debug") ) { Write-Debug "Debugging is enabled, using cached raw html if available" if ( ls $cacheDir | ? { $_.Name -eq "$($SerialNumber).raw.html" } ) { Write-Debug "Raw HTML read from cache" $htData = Get-Content (Join-Path $cacheDir "$($SerialNumber).raw.html") } Else { Write-Debug "Download PartSurfer HTML data" $htData = $htClient.DownloadString("$($partSurferQueryUrl)$($SerialNumber)") Write-Debug "Clean line breaks, tabs, multiple spaces, and blank space between tags" $htData = CleanHtml( $htData ) Write-Debug "Raw HTML written to cache" Set-Content (Join-Path $cacheDir "$($SerialNumber).raw.html") $htData } } Else { Write-Debug "Download PartSurfer HTML data" $htData = $htClient.DownloadString("$($partSurferQueryUrl)$($SerialNumber)") Write-Debug "Clean line breaks, tabs, multiple spaces, and blank space between tags" $htData = CleanHtml( $htData ) } 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 ".*(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).*" ) { $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." } } 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 foreach ( $trMatch in $trMatches.Matches ){ $tdMatches = Select-String "" -inputObject $trMatch -AllMatches $warrData = $tdMatches.Matches | % { if ( $_ -notmatch "(rowspan|color)" ) { $_.value -replace "]*>" -replace "Wty: ", "" -replace "HP ", "" -replace "HW Maintenance ", "HWM " -replace "Support ?(for )?", "" } } if ( $warrData[1].Length -or $warrData[2].Length ) { <# $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 $hpProduct.$BomType | Add-Member NoteProperty $partObject.PartNumber $partObject #> $warrObject = New-Object psObject $warrObject | Add-Member NoteProperty Start ( '{0:MMM d yyyy}' -f ( Get-Date $warrData[1] ) ) $warrObject | Add-Member NoteProperty End ( '{0:MMM d yyyy}' -f ( Get-Date $warrData[2] ) ) $hpProduct.Warranties | Add-Member NoteProperty $warrData[0] $warrObject #$hpProduct.Warranties | Add-Member NoteProperty $warrData[0] @{ Start = ( '{0:MMM d yyyy}' -f ( Get-Date $warrData[1] ) ); End = ( '{0:MMM d yyyy}' -f ( Get-Date $warrData[2] ) ) } } Else { $hpProduct.Warranties | Add-Member NoteProperty $warrData[0] "" } } <# while ($htData -match "]*>\s*([\w\s:]*)\s*\s*]*>\s*(\d{1,2} \w{3} \d{4})\s*\s*]*>\s*(\d{1,2} \w{3} \d{4})\s*" ) { $warranty += $Matches[1..3] $htData = $htData.Replace($Matches[0],1) } $warranty = $warranty -replace "wty: |HWM |HP |HW |Maintenance |Support |for| " ### Add warranty data to psObject $i = 0 while ( $i -lt $warranty.Length ){ #"$($warranty[$i])`t`tStart: $($warranty[$i+1])`t`tEnd: $($warranty[$i+2])" $hpProduct | Add-Member NoteProperty "Warranty$($warranty[$i])" @{ Start = ( '{0:MMM d yyyy}' -f ( Get-Date $warranty[$i+1] ) ); End = ( '{0:MMM d yyyy}' -f ( Get-Date $warranty[$i+2] ) ) } $i += 3 }#> $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") return $hpProduct } #Import line is potentially error prone, make it more specific later on if ( $useCache ) { $hpProduct = Import-Clixml ( Join-Path $cacheDir "$($SerialNumber)_*" ) } else { $hpProduct = downloadProductData } 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 ( "Start" -in ($hpProduct.Warranties.$warranty | Get-Member | % { $_.Name } ) ) { $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 } } }