<# .NOTES Written by Ian Cammarata http://ian.cammarata.us Get-HpProductInfo.ps1 v0.3 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: -Make it a proper PowerShell script (receive piped input, input validation, multiple SNs at once) -Cache objects to an XML file for faster loading if the same SN is queried again. -Multi-threading the http downloads and multiple retry attemps (partsufer is so unreliable lately) -Option to filter parts output 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, #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 ###### Set Constants #<# ### uncomment this line for testing in ISE after initial run Set-Variable productObjectVersion -option Constant -value 0.2 Set-Variable partSurferQueryUrl -option Constant -value "http://partsurfer.hp.com/Search.aspx?searchText=" #Set-Variable hpWarrantyQueryUrl -option Constant -value "http://h20000.www2.hp.com/bizsupport/TechSupport/WarrantyResults.jsp?lang=en&cc=us&country=US&find=Display+Warranty+Information+%C2%BB" Set-Variable hpWarrantyStartUrl -option Constant -value "http://h20566.www2.hp.com/portal/site/hpsc/template.PAGE/public/wc/results/" Set-Variable hpWarrantyQueryUrl -option Constant -value "http://h20566.www2.hp.com/portal/site/hpsc/template.PAGE/public/wc/results/?spf_p.tpst=wcMain&spf_p.prp_wcMain=wsrp-navigationalState%3Daction%253Dsummary&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 { Foreach ($SerialNumber in $SerialNumber) { $SerialNumber = $SerialNumber.Trim().ToUpper() ### Begin Check Cache $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)" ### End Check Cache function downloadProductData () { ### Create WebClient and cookie needed for PartSurfer $htClient = New-Object System.Net.Webclient $htClient.Headers.Set([System.Net.HttpRequestHeader]::Cookie, "Country=United%20States") ### Download and clean PartSurfer HTML so it can be parsed into XML $ErrorActionPreference = "Stop" #Needed so script won't continue if the web server returns an error $htData = $htClient.DownloadString("$($partSurferQueryUrl)$($SerialNumber)") #Clean line breaks, tabs, multiple spaces, and blank space between tags $htData = $htData -replace "\n", "" -replace "\t", " " -replace " ", "" -replace "> <", "><" ### Create psObject represinting the product $hpProduct = New-Object psObject ### Load generic sku data into the object $hpProduct | Add-Member NoteProperty SerialNumber $SerialNumber $null = $htData -match "(.*?)" $hpProduct | Add-Member NoteProperty ProductNumber $Matches[1] $null = $htData -match "(.*?)" $hpProduct | Add-Member NoteProperty Description $Matches[1] ### Add parts to psObject (Check boxes are no longer supplied by the site to determine part availability) $hpProduct | Add-Member NoteProperty Parts (New-Object psObject) $trMatches = Select-String ".*?" -inputObject $htData -AllMatches foreach ( $trMatch in $trMatches.Matches ){ IF ( $trMatch.Value -notmatch ".*(no longer supplied|Not Available).*" ) { $spanMatches = Select-String "(.*?)" -inputObject $trMatch.Value -AllMatches #"$($spanMatches.Matches[0].Groups[1].Value)`t$($spanMatches.Matches[1].Groups[1].Value)" $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.Parts | Add-Member NoteProperty $partObject.PartNumber $partObject } } <# ### Download and parse warranty info -- Page is too messy to clean for XML casting, will just scrape with regex $ErrorActionPreference = "Stop" #Needed so script won't continue if the web server returns an error $htData = $htClient.DownloadString("$($hpWarrantyQueryUrl)&sn=$($hpProduct.SerialNumber)&pn=$($hpProduct.ProductNumber)") $ErrorActionPreference = "Continue" $warranty = @() $htData = $htData -replace "
" 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 }#> ### Download and parse warranty i nfo <# Parametersapplication/x-www-form-urlencoded rows[0].item.countryCode US rows[0].item.serialNumber CND120CJH0 rows[1].item.countryCode US rows[1].item.serialNumber rows[2].item.countryCode US rows[2].item.serialNumber rows[3].item.countryCode US rows[3].item.serialNumber rows[4].item.countryCode US rows[4].item.serialNumber rows[5].item.countryCode US rows[5].item.serialNumber rows[6].item.countryCode US rows[6].item.serialNumber rows[7].item.countryCode US rows[7].item.serialNumber rows[8].item.countryCode US rows[8].item.serialNumber rows[9].item.countryCode US rows[9].item.serialNumber submitButton Submit ################################################### Source rows%5B0%5D.item.serialNumber=CND120CJH0&rows%5B0%5D.item.countryCode=US&rows%5B1%5D.item.serialNumber=&rows%5B1%5D.item.countryCode=US&rows%5B2%5D.item.serialNumber=&rows%5B2%5D.item.countryCode=US&rows%5B3%5D.item.serialNumber=&rows%5B3%5D.item.countryCode=US&rows%5B4%5D.item.serialNumber=&rows%5B4%5D.item.countryCode=US&rows%5B5%5D.item.serialNumber=&rows%5B5%5D.item.countryCode=US&rows%5B6%5D.item.serialNumber=&rows%5B6%5D.item.countryCode=US&rows%5B7%5D.item.serialNumber=&rows%5B7%5D.item.countryCode=US&rows%5B8%5D.item.serialNumber=&rows%5B8%5D.item.countryCode=US&rows%5B9%5D.item.serialNumber=&rows%5B9%5D.item.countryCode=US&submitButton=Submit #> <# wc.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded"; string HtmlResult = wc.UploadString(URI, myParameters); #> <# $ErrorActionPreference = "Stop" #Needed so script won't continue if the web server returns an error #$htClient.Headers["Content-Type"] = "application/x-www-form-urlencoded" $htClient.Headers.Set( "Content-Type", "application/x-www-form-urlencoded" ) #$htData = $htClient.DownloadString("$($partSurferQueryUrl)$($SerialNumber)") $htData = $htClient.UploadString( $hpWarrantyQueryUrl, "rows[0].item.serialNumber=$($SerialNumber)&rows[0].item.countryCode=US" ) $htData = -join $htData[$htData.IndexOf("")+6)] $htData = $htData -replace '(?sx:)' -replace '(?sx:)' $htData = $htData -replace ' & ', " and " -replace "&\w{2,6};" #> $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)" "Description:`t$($hpProduct.Description)" <#"-" * $lineLen "Warranty:" foreach ( $warrantyItem in $hpProduct | Get-Member | ? { $_.Name -like "Warranty*" } | % { $_.Name } ) { "$($warrantyItem):" " Start: $($hpProduct.$warrantyItem.Start) End: $($hpProduct.$warrantyItem.End)" }#> "-" * $lineLen $parts = $hpProduct.Parts | get-member -MemberType "NoteProperty" "Available Parts: $($parts.Length)" "-" * $lineLen foreach ( $part in $parts | % { $_.Name } ) { "$($hpProduct.Parts.$part.PartNumber) - $($hpProduct.Parts.$part.Description)" } "-"*$lineLen } } }