Automating with PowerShell: Automating Warranty information reporting.

One of the reddits I frequent has been seeing a lot of complaints lately about warranty information being incomplete or there’s complaints about the pricing of warranty information products. Most of these complaints are aimed at a specific product which is showing very shady sales tactics and general bad business practices lately.

I figured I would try to take up these complaints and solve them with a PowerShell script. This script grabs the warranty information for most major manufactures. It will generate a warranty report based on the input data. The input data can either be a CSV file or the Autotask PSA.

You can also upload the warranty date back to Autotask to keep your warranty information in sync too. Currently I’ve only made support for Autotask but I’m willing to create one for CW too if there is enough interest 🙂

So lets get started.

Prerequisites

Before we can dive into the script we’ll have to collect some API keys. These keys will be used to get the warranty information at the vendors, or there are some “gotcha’s” you must know beforehand.

When you have Dell as a vendor:

  • Go to the Dell TechDirect website and register if you do not yet have an account. Complete the enrollment.
  • After registration, browse to the Dell TechDirect API enrollment page and wait for approval. This is a manual procedure so can take a day or two.
  • When the approval has been given, request a new API key and save this in a secure location.

When you have HP as a vendor:

Officially the HP API has been disabled because it was getting hammered by requests. The unofficial API is still available though so this script is based on that API.

When you have Microsoft as a vendor:

So Microsoft officially does not have a warranty lookup tool, but I found the Surface Diagnostic App does have a way to have programmatic access to the warranty environment. I reverse engineered this method. This is an unofficial and unsupported API so it might not function in the future. I’m quite proud of this one because as far as I can see, people have only achieved MS warranty checks by evading the CAPTCHA.

When you have Lenovo as a vendor:

You don’t have to do anything 🙂 Lenovo has an open API.

The Script

So now that we have our API keys and data, we can start feeding the script information. This can be via a CSV file or via Autotask. The CSV file should have the following information:

serialnumber,vendor,client
1234,HP,contoso
1234,Dell,toiletpaperco
1234,MS,contoso
1234,Lenovo,johndoe inc

If you are using Autotask, fill in the API integration key from your API user, and log in when your username/password is requested.

$source = "CSV" #AT, CSV, ITG, CW
##### Sync Settings
$SyncWithSource = $true  #Sync status warranty dates/status back to PSA/Management system. Only works with dynamic sources like ITG and AT.
$OverwriteWarranty = $true #Overwrites the date already found in AT with the one based on this API, unless the API could not find information.
$CreateHTMLReport = $true #Creates an HTML report.
###### File locations
$ReportsLocation = "C:\temp\reports" #Only required if Reporting is enabled.
$sourcefile = "C:\temp\temp.csv" #only required if source is not autotask.
$ATLogPath = "C:\temp\AT.txt" #Only used to log which objects have been synced with AT as AT does not have a audit log.
##### AT API Settings
$ATAPIKey = "Your-API-Key-For-Autotask" #only required if source is Autotask.
##### ITG API Settings
$ITGAPIKey = "Your-API-Key-For-ITG"  #only required if source is ITG
$ITGAPIURL = "https://api.eu.itglue.com" #only required if source is ITG
##### CW API Settings
$CWAPIURL = "https://api-staging.connectwisedev.com/v4_6_release/apis/3.0" #https://developer.connectwise.com/Best_Practices/Manage_Cloud_URL_Formatting?mt-learningpath=manage
$CWApiKeyPublic = "CWPublicKey" #Only required if source is CW
$CWApiKeyPrivate = "CwPrivateKey" #Only required if source is CW
$CWcompanyid = "CompanyID_1" #Only required if source is CW
##### Warranty Vendor API Keys
$DellClientID = "Dell-Client-ID"
$DellClientSecret = "Dell-Client-Secret"



function get-HPWarranty([Parameter(Mandatory = $true)]$SourceDevice, $Client) {
    $MWSID = (invoke-restmethod -uri 'https://support.hp.com/us-en/checkwarranty/multipleproducts/' -SessionVariable 'session' -Method get) -match '.*mwsid":"(?<wssid>.*)".*'
    $HPBody = " { `"gRecaptchaResponse`":`"`", `"obligationServiceRequests`":[ { `"serialNumber`":`"$SourceDevice`", `"isoCountryCde`":`"US`", `"lc`":`"EN`", `"cc`":`"US`", `"modelNumber`":null }] }"
 
    $HPReq = Invoke-RestMethod -Uri "https://support.hp.com/hp-pps-services/os/multiWarranty?ssid=$($matches.wssid)" -WebSession $session -Method "POST" -ContentType "application/json" -Body $HPbody
    if ($HPreq.productWarrantyDetailsVO.warrantyResultList.obligationStartDate) {
        $WarObj = [PSCustomObject]@{
            'Serial'                = $SourceDevice
            'Warranty Product name' = $hpreq.productWarrantyDetailsVO.warrantyResultList.warrantyType | Out-String
            'StartDate'             = $hpreq.productWarrantyDetailsVO.warrantyResultList.obligationStartDate | sort-object | select-object -last 1
            'EndDate'               = $hpreq.productWarrantyDetailsVO.warrantyResultList.obligationEndDate | sort-object | select-object -last 1
            'Warranty Status'       = $hpreq.productWarrantyDetailsVO.obligationStatus
            'Client'                = $Client
        }
    }
    else {
        $WarObj = [PSCustomObject]@{
            'Serial'                = $SourceDevice
            'Warranty Product name' = 'Could not get warranty information'
            'StartDate'             = $null
            'EndDate'               = $null
            'Warranty Status'       = 'Could not get warranty information'
            'Client'                = $Client
        }
    }
    return $WarObj
}
function get-DellWarranty([Parameter(Mandatory = $true)]$SourceDevice, $client) {
    $today = Get-Date -Format yyyy-MM-dd
    $AuthURI = "https://apigtwb2c.us.dell.com/auth/oauth/v2/token"
    if ($Global:TokenAge -lt (get-date).AddMinutes(-55)) { $global:Token = $null }
    If ($null -eq $global:Token) {
        $OAuth = "$global:DellClientID`:$global:DellClientSecret"
        $Bytes = [System.Text.Encoding]::ASCII.GetBytes($OAuth)
        $EncodedOAuth = [Convert]::ToBase64String($Bytes)
        $headersAuth = @{ "authorization" = "Basic $EncodedOAuth" }
        $Authbody = 'grant_type=client_credentials'
        $AuthResult = Invoke-RESTMethod -Method Post -Uri $AuthURI -Body $AuthBody -Headers $HeadersAuth
        $global:token = $AuthResult.access_token
        $Global:TokenAge = (get-date)
    }

    $headersReq = @{ "Authorization" = "Bearer $global:Token" }
    $ReqBody = @{ servicetags = $SourceDevice }
    $WarReq = Invoke-RestMethod -Uri "https://apigtwb2c.us.dell.com/PROD/sbil/eapi/v5/asset-entitlements" -Headers $headersReq -Body $ReqBody -Method Get -ContentType "application/json"
    $warlatest = $warreq.entitlements.enddate | sort-object | select-object -last 1 
    $WarrantyState = if ($warlatest -le $today) { "Expired" } else { "OK" }
    if ($warreq.entitlements.serviceleveldescription) {
        $WarObj = [PSCustomObject]@{
            'Serial'                = $SourceDevice
            'Warranty Product name' = $warreq.entitlements.serviceleveldescription -join "`n"
            'StartDate'             = (($warreq.entitlements.startdate | sort-object -Descending | select-object -last 1) -split 'T')[0]
            'EndDate'               = (($warreq.entitlements.enddate | sort-object | select-object -last 1) -split 'T')[0]
            'Warranty Status'       = $WarrantyState
            'Client'                = $Client
        }
    }
    else {
        $WarObj = [PSCustomObject]@{
            'Serial'                = $SourceDevice
            'Warranty Product name' = 'Could not get warranty information'
            'StartDate'             = $null
            'EndDate'               = $null
            'Warranty Status'       = 'Could not get warranty information'
            'Client'                = $Client
        }
    }
    return $WarObj
}
function get-LenovoWarranty([Parameter(Mandatory = $true)]$SourceDevice, $client) {
    $today = Get-Date -Format yyyy-MM-dd
    $APIURL = "https://ibase.lenovo.com/POIRequest.aspx"
    $SourceXML = "xml=<wiInputForm source='ibase'><id>LSC3</id><pw>IBA4LSC3</pw><product></product><serial>$SourceDevice</serial><wiOptions><machine/><parts/><service/><upma/><entitle/></wiOptions></wiInputForm>"
    $Req = Invoke-RestMethod -Uri $APIURL -Method POST -Body $SourceXML -ContentType 'application/x-www-form-urlencoded'
    if ($req.wiOutputForm) {
        $warlatest = $Req.wiOutputForm.warrantyInfo.serviceInfo.wed | sort-object | select-object -last 1 
        $WarrantyState = if ($warlatest -le $today) { "Expired" } else { "OK" }
         
        $WarObj = [PSCustomObject]@{
            'Serial'                = $Req.wiOutputForm.warrantyInfo.machineinfo.serial
            'Warranty Product name' = $Req.wiOutputForm.warrantyInfo.machineinfo.productname -join "`n"
            'StartDate'             = $Req.wiOutputForm.warrantyInfo.serviceInfo.warstart | sort-object -Descending | select-object -last 1
            'EndDate'               = $Req.wiOutputForm.warrantyInfo.serviceInfo.wed | sort-object | select-object -last 1
            'Warranty Status'       = $WarrantyState
            'Client'                = $Client
        }
    }
    else {
        $WarObj = [PSCustomObject]@{
            'Serial'                = $SourceDevice
            'Warranty Product name' = 'Could not get warranty information'
            'StartDate'             = $null
            'EndDate'               = $null
            'Warranty Status'       = 'Could not get warranty information'
            'Client'                = $Client
        }
    }
    return $WarObj
 
 
}
function Get-MSWarranty([Parameter(Mandatory = $true)]$SourceDevice, $client) {
    $body = ConvertTo-Json @{
        sku          = "Surface_"
        SerialNumber = $SourceDevice
        ForceRefresh = $false
    }
    $today = Get-Date -Format yyyy-MM-dd
    $PublicKey = Invoke-RestMethod -Uri 'https://surfacewarrantyservice.azurewebsites.net/api/key' -Method Get
    $AesCSP = New-Object System.Security.Cryptography.AesCryptoServiceProvider 
    $AesCSP.GenerateIV()
    $AesCSP.GenerateKey()
    $AESIVString = [System.Convert]::ToBase64String($AesCSP.IV)
    $AESKeyString = [System.Convert]::ToBase64String($AesCSP.Key)
    $AesKeyPair = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes("$AESIVString,$AESKeyString"))
    $bodybytes = [System.Text.Encoding]::UTF8.GetBytes($body)
    $bodyenc = [System.Convert]::ToBase64String($AesCSP.CreateEncryptor().TransformFinalBlock($bodybytes, 0, $bodybytes.Length))
    $RSA = New-Object System.Security.Cryptography.RSACryptoServiceProvider
    $RSA.ImportCspBlob([System.Convert]::FromBase64String($PublicKey))
    $EncKey = [System.Convert]::ToBase64String($rsa.Encrypt([System.Text.Encoding]::UTF8.GetBytes($AesKeyPair), $false))
     
    $FullBody = @{
        Data = $bodyenc
        Key  = $EncKey
    } | ConvertTo-Json
     
    $WarReq = Invoke-RestMethod -uri "https://surfacewarrantyservice.azurewebsites.net/api/v2/warranty" -Method POST -body $FullBody -ContentType "application/json"
    if ($WarReq.warranties) {
        $WarrantyState = foreach ($War in ($WarReq.warranties.effectiveenddate -split 'T')[0]) {
            if ($War -le $today) { "Expired" } else { "OK" }
        }
        $WarObj = [PSCustomObject]@{
            'Serial'                = $SourceDevice
            'Warranty Product name' = $WarReq.warranties.name -join "`n"
            'StartDate'             = (($WarReq.warranties.effectivestartdate | sort-object -Descending | select-object -last 1) -split 'T')[0]
            'EndDate'               = (($WarReq.warranties.effectiveenddate | sort-object | select-object -last 1) -split 'T')[0]
            'Warranty Status'       = $WarrantyState
            'Client'                = $Client
        }
    }
    else {
        $WarObj = [PSCustomObject]@{
            'Serial'                = $SourceDevice
            'Warranty Product name' = 'Could not get warranty information'
            'StartDate'             = $null
            'EndDate'               = $null
            'Warranty Status'       = 'Could not get warranty information'
            'Client'                = $Client
        }
    }
    return $WarObj
}
 
function  Get-WarrantyCSV($sourcefile) {
    write-host "Source is CSV file. Grabbing all devices." -ForegroundColor Green
    $CSVLines = import-csv -path $sourcefile -Delimiter ","
    $warrantyObject = foreach ($Line in $CSVLines) {
        switch ($line.vendor) {
            HP { get-HPWarranty -SourceDevice $line.SerialNumber -Client $line.client }
            Dell { get-DellWarranty -SourceDevice $line.SerialNumber -Client $line.client }
            Lenovo { get-LenovoWarranty -SourceDevice $line.SerialNumber -Client $line.client }
            MS { Get-MSWarranty -SourceDevice $line.SerialNumber -Client $line.client }
        }
    }
    return $warrantyObject
}
 
function Get-WarrantyAutotask($APIKey) {
    write-host "Source is Autotask." -ForegroundColor Green
    If (Get-Module -ListAvailable -Name "Autotask") { Import-module "Autotask" } Else { install-module "Autotask" -Force }
    $Credential = Get-Credential -Message "Enter your Autotask Credentials"
    remove-module autotask
    Import-Module Autotask -ArgumentList $Credential, $global:ATAPIKey
    write-host "Logging into Autotask. Grabbing all client information." -ForegroundColor "Green"
    $AllClients = $AllAccounts = Get-AtwsAccount -All | Where-Object { $_.Active -eq $true }
    write-host "Client information found. Grabbing all devices" -ForegroundColor "Green"
    $AllDevices = Get-AtwsInstalledProduct -All | Where-Object { $_.Active -eq $true -and $null -ne $_.SerialNumber }
    write-host "Collecting information. This can take a long time." -ForegroundColor "Green"
    $i = 0
    $warrantyObject = foreach ($Device in $AllDevices) {
        $i++
        Write-Progress -Activity "Grabbing Warranty information" -status "Processing $($device.serialnumber). Device $i of $($Alldevices.Count)" -percentComplete ($i / $Alldevices.Count * 100)
        $Client = ($AllClients | Where-Object { $_.id -eq $device.AccountID }).AccountName
        #We use a guess-smart method for serialnumbers. 
        #Dell is always 7, Lenovo is always 8, 10 is HP, 12 is Surface. 
        #This is because we cannot safely find the manafacture in the AT info.
        switch ($device.SerialNumber.Length) {
            7 { $WarState = get-DellWarranty -SourceDevice $device.SerialNumber -client $Client }
            8 { $WarState = get-LenovoWarranty -SourceDevice $device.SerialNumber -client $Client }
            10 { $WarState = get-HPWarranty  -SourceDevice $device.SerialNumber -client $Client }
            12 { $WarState = Get-MSWarranty  -SourceDevice $device.SerialNumber -client $Client }
        }
        if ($script:SyncWithSource -eq $true) {
            switch ($script:OverwriteWarranty) {
                $true {
                    if ($null -ne $warstate.EndDate) {
                        $device | Set-AtwsInstalledProduct -WarrantyExpirationDate $warstate.EndDate
                        "$Client / $($device.SerialNumber) with AT ID $($device.id) warranty has been overwritten to $($warstate.EndDate)" | out-file $script:ATLogPath -Append -Force
                    }
                     
                }
                $false { 
                    if ($null -eq $device.WarrantyExpirationDate -and $null -ne $warstate.EndDate) { 
                        $device | Set-AtwsInstalledProduct -WarrantyExpirationDate $warstate.EndDate 
                        "$Client / $($device.SerialNumber) with AT ID $($device.id) warranty has been set to $($warstate.EndDate)" | out-file $script:ATLogPath -Append -Force
                    } 
                }
            }
        }
        $WarState
    }
 
    return $warrantyObject
}
function  Get-WarrantyITG() {
    write-host "Source is IT-Glue. Grabbing all devices." -ForegroundColor Green
    If (Get-Module -ListAvailable -Name "ITGlueAPI") { 
        Import-module ITGlueAPI 
    }
    Else { 
        Install-Module ITGlueAPI -Force
        Import-Module ITGlueAPI
    }
    #Settings IT-Glue logon information
    Add-ITGlueBaseURI -base_uri $Global:ITGAPIURL
    Add-ITGlueAPIKey  $Global:ITGAPIKey
    write-host "Getting IT-Glue configuration list" -foregroundColor green
    $i = 0
    $AllITGlueConfigs = @()
    do {
        $AllITGlueConfigs += (Get-ITglueconfigurations -page_size 1000 -page_number $i).data
        $i++
        Write-Host "Retrieved $($AllITGlueConfigs.count) configurations" -ForegroundColor Yellow
    }while ($AllITGlueConfigs.count % 1000 -eq 0 -and $AllITGlueConfigs.count -ne 0) 
     
    $warrantyObject = foreach ($device in $AllITGlueConfigs) {
        $i++
        Write-Progress -Activity "Grabbing Warranty information" -status "Processing $($device.attributes.'serial-number'). Device $i of $($AllITGlueConfigs.Count)" -percentComplete ($i / $AllITGlueConfigs.Count * 100)
        $Client = ($AllClients | Where-Object { $_.id -eq $device.AccountID }).AccountName
        $client = $device.attributes.'organization-name'
        switch ($device.attributes.'serial-number'.Length) {
            7 { $WarState = get-DellWarranty -SourceDevice $device.attributes.'serial-number' -client $Client }
            8 { $WarState = get-LenovoWarranty -SourceDevice $device.attributes.'serial-number' -client $Client }
            10 { $WarState = get-HPWarranty  -SourceDevice $device.attributes.'serial-number' -client $Client }
            12 { $WarState = Get-MSWarranty  -SourceDevice $device.attributes.'serial-number' -client $Client }
        }
        if ($script:SyncWithSource -eq $true) {
            $FlexAssetBody = @{
                "type"       = "configurations"
                "attributes" = @{
                    'warranty-expires-at' = $warstate.EndDate
                } 
            }
            switch ($script:OverwriteWarranty) {
                $true {
                    if ($null -ne $warstate.EndDate) {
                        Set-ITGlueConfigurations -id $device.id -data $FlexAssetBody
                    }
                     
                }
                $false { 
                    if ($null -eq $device.WarrantyExpirationDate -and $null -ne $warstate.EndDate) { 
                        Set-ITGlueConfigurations -id $device.id -data $FlexAssetBody
                    } 
                }
            }
        }
        $WarState
    }
    return $warrantyObject
}
 
function  Get-WarrantyCW() {
    write-host "Source is Connectwise Manage. Grabbing all devices." -ForegroundColor Green
    $Base64Key = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes("$($Global:CWcompanyid)+$($Global:CWApiKeyPublic):$($global:CWApiKeyPrivate)"))
 
    $Header = @{
        'clientId'      = '3613dda6-fa25-49b9-85fb-7aa2b628befa' #This is the warranty script client id. Do not change. 
        'Authorization' = "Basic $Base64Key"
        'Content-Type'  = 'application/json'
    }
    $i = 0
    $Devices = @()
    do {
        $Devices += invoke-restmethod -headers $header -method GET -uri "$($Global:CWAPIURL)/company/configurations?pageSize=1000&page=$i&quot;
        $i++
        Write-Host "Retrieved $($devices.count) configurations" -ForegroundColor Yellow
    }while ($devices.count % 1000 -eq 0 -and $devices.count -ne 0) 
 
    $warrantyObject = foreach ($device in $Devices) {
        $i++
        Write-Progress -Activity "Grabbing Warranty information" -status "Processing $($device.serialnumber). Device $i of $($devices.Count)" -percentComplete ($i / $Devices.Count * 100)
        $client = $device.company.name
        switch ($device.serialnumber.Length) {
            7 { $WarState = get-DellWarranty -SourceDevice $device.serialnumber -client $Client }
            8 { $WarState = get-LenovoWarranty -SourceDevice $device.serialnumber -client $Client }
            10 { $WarState = get-HPWarranty  -SourceDevice $device.serialnumber -client $Client }
            12 { $WarState = Get-MSWarranty  -SourceDevice $device.serialnumber -client $Client }
        }
        if ($script:SyncWithSource -eq $true) {
            if (!$device.warrantyExpirationDate) {
                $device | Add-Member -NotePropertyName "warrantyExpirationDate" -NotePropertyValue "$($WarState.enddate)T00:00:00Z"
            }
            else { 
                $device.warrantyExpirationDate = "$($WarState.enddate)T00:00:00Z"
            }
            $CWBody = $device | ConvertTo-Json
            switch ($script:OverwriteWarranty) {
                $true {
                    if ($null -ne $warstate.EndDate) {
                        invoke-restmethod -headers $header -method put -uri "$($Global:CWAPIURL)/company/configurations/$($device.id)" -body $CWBody
                    }
                     
                }
                $false { 
                    if ($null -eq $device.WarrantyExpirationDate -and $null -ne $warstate.EndDate) { 
                        invoke-restmethod -headers $header -method put -uri "$($Global:CWAPIURL)/company/configurations/$($device.id)" -body $CWBody
                    } 
                }
            }
        }
        $WarState
    }
    return $warrantyObject
}
 
 
switch ($source) {
    AT { $warrantyObject = Get-WarrantyAutotask -APIKey $ATAPIKey | Sort-Object -Property Client }
    CSV { $warrantyObject = Get-WarrantyCSV -Sourcefile $sourcefile | Sort-Object -Property Client }
    ITG { $warrantyObject = Get-WarrantyITG | Sort-Object -Property Client }
    CW { $warrantyObject = Get-WarrantyCW | Sort-Object -Property Client }
}
write-host "Done updating warrenties. Generating reports if required." -ForegroundColor Green
$head = @"
<script>
function myFunction() {
    const filter = document.querySelector('#myInput').value.toUpperCase();
    const trs = document.querySelectorAll('table tr:not(.header)');
    trs.forEach(tr => tr.style.display = [...tr.children].find(td => td.innerHTML.toUpperCase().includes(filter)) ? '' : 'none');
  }</script>
<Title>Warranty Report</Title>
<style>
body { background-color:#E5E4E2;
      font-family:Monospace;
      font-size:10pt; }
td, th { border:0px solid black; 
        border-collapse:collapse;
        white-space:pre; }
th { color:white;
    background-color:black; }
table, tr, td, th {
     padding: 2px; 
     margin: 0px;
     white-space:pre; }
tr:nth-child(odd) {background-color: lightgray}
table { width:95%;margin-left:5px; margin-bottom:20px; }
h2 {
font-family:Tahoma;
color:#6D7B8D;
}
.footer 
{ color:green; 
 margin-left:10px; 
 font-family:Tahoma;
 font-size:8pt;
 font-style:italic;
}
#myInput {
  background-image: url('https://www.w3schools.com/css/searchicon.png'); /* Add a search icon to input */
  background-position: 10px 12px; /* Position the search icon */
  background-repeat: no-repeat; /* Do not repeat the icon image */
  width: 50%; /* Full-width */
  font-size: 16px; /* Increase font-size */
  padding: 12px 20px 12px 40px; /* Add some padding */
  border: 1px solid #ddd; /* Add a grey border */
  margin-bottom: 12px; /* Add some space below the input */
}
</style>
"@
   
$PreContent = @"
<H1> Warranty Report </H1> <br>
   
Please consult the report for more information. you can use the search window to find a specific device, date, or warranty state.
<br/>
<br/>
    
<input type="text" id="myInput" onkeyup="myFunction()" placeholder="Search...">
"@
   
 
if ($CreateHTMLReport -eq $true) {
    $CheckReportFolder = Test-Path($ReportsLocation)
    if (!$CheckReportFolder) { new-item -ItemType Directory -Path $ReportsLocation -Force | Out-Null }
    foreach ($client in $warrantyObject.client | Select-Object -Unique) {
        write-host "Generating report for $Client at $($ReportsLocation)\$client.html" -ForegroundColor Green
        $warrantyObject | Where-Object { $_.Client -eq $client } | convertto-html -Head $head -precontent $precontent | out-file "$($ReportsLocation)\$client.html"
    }
 
}

After executing the script, the HTML reports will look like this:

And that’s it! the script isn’t perfect yet and could use some more error handling which I’ll work on in the coming weeks but I really think this is a fantastic solution to get rid of warranty information providers that are asking big bucks. 🙂

So as always, Happy PowerShelling!

Update 1: Added ITGlue as source.

Update 2: Added CW as resource

Update 3: Added some better logic for the script, also the script is on Github now! https://github.com/KelvinTegelaar/PowerShellWarrantyReports

76 thoughts on “Automating with PowerShell: Automating Warranty information reporting.

  1. Robin Thomas

    So I just ran the script and it updated all my datto resources. This is pretty dang cool. Thanks Kevin! We’re canceling our warranty master subscription now.

    Reply
    1. Tim Bixley

      Thanks Kevin, we will be testing this shortly to hopefully remove warrantymaster. Do you know if it can write the data back to Datto RMM even if via the PSA writeback?

      Reply
  2. Krystal

    Thank you so much for sharing. Definitely interested and would appreciate if there was one for Connectiwse. Please let me know

    Reply
  3. Chelsea

    Hi! Thanks for creating such a cool tool for everyone to use!
    I was wondering if this integrates with Ninja PSA?
    Thanks!
    Chelsea

    Reply
  4. Chelsea

    Oops!! I asked if you had Ninja PSA capability when I really meant does this work with Sherpadesk PSA. Please let me know, sorry about the confusion!

    Reply
  5. Daniel Nunns

    Hi Kelvin

    Thanks for your hard work, prior to going with Warranty Master myself, I was looking at a way to do this with scripting. I immediately hit the HP API roadblock and burnt a day, without finding your workaround.

    I have elected Kaseya as our central point of hardware information, so any advancements on that front would be fantastic, however, I believe I would be able to repurpose your script with a procedure.

    Thanks for sharing!

    Cheers

    Reply
  6. carlo

    Hi Kelvin,

    This is absolutely awesome! Currently working on implementing this into our systems. One thing I did notice was that one or two test machines for the Dell API check failed due to using old TLS version. I added the following line to force the script to use TLS 1.2 and it works perfectly: [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

    Reply
  7. Pingback: ICYMI: PowerShell Week of 08-May-2020 | PowerShell.org

  8. Pingback: PowerShell SnippetRace 19-2020 | | PowerShell Usergroup Austria

  9. David

    Dank u Kelvin

    Does the script update Autotask PSA or IT Glue warranty dates or does it only report in an HTML?

    Reply
  10. Pingback: Documenting and monitoring blogs updates - CyberDrain

  11. Dave M

    Thank you so much for this.

    When I use CW as a source, I seem to be missing something , as I keep getting the below error.
    I have confirmed that my CW api key is correct.

    Source is Connectwise Manage. Grabbing all devices.
    invoke-restmethod : Invalid Token
    At Warranty information reporting.ps1:303 char:21
    + … $Devices += invoke-restmethod -headers $header -method GET -uri “$($G …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-RestMethod], WebException
    + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand
    Retrieved 0 configurations
    Done updating warrenties. Generating reports if required.

    Line 303 is:
    $Devices += invoke-restmethod -headers $header -method GET -uri “$($Global:CWAPIURL)/company/configurations?pageSize=1000&page=$i”

    Reply
  12. Henk Jansen

    Hi Kelvin,

    Verry nice script, is it also possible to store and run this script by using Azure functions?

    So thay way it could be automated by a scheduled job.

    Reply
      1. Henk Jansen

        Nice! But i’m unable to get it up and running.
        Are there specific requirements needed in the Azure Function?

        Reply
        1. Kelvin Tegelaar Post author

          It really depends on which version you’re using and how much you’re feeding the script. (CW, CSV, Autotask, etc) Standard functions have a runtime of 10 minutes. It might be better to run it with Azure Automation runbooks.

          for functions; remember to enable 64bit mode.

          Reply
  13. Toby

    Hi Kelvin,
    Thank you for yet another amazing bit of code! Totally changes the way we get the warranty information now 🙂

    I had to adjust a couple of bits to get this working for me, for instance there may have a typo in the Get-WarrantyAutotask function where “$AllClients” and/or “$AllAccounts” is being set (line 193):

    $AllClients = $AllAccounts = Get-AtwsAccount -All | Where-Object { $_.Active -eq $true }

    I dropped the ‘$AllAccounts =’ part as it doesn’t appear to be used and that got this working.

    I also had an issue with the Get-MSWarranty function and found I had to specify the parameter $SourceDevice as a string before it would work properly – not sure if others found the same of it’s just a quirk of Datto PSA perhaps?

    In using this against our client base, I’ve found a few HP devices where the returned warranty expiration date is ‘N/A’ – this then fails on the writeback to our PSA as it cannot be converted to a system datetime. To prevent this, I added an additional “-and $warstate.EndDate -ne ‘N/A'” to the switch used in the various “if ($script:SyncWithSource -eq $true)” sections of code.

    I was looking forward to seeing all the reports at the end of the process but spotted the wrong variable is used on line 413 – the test should be against the variable $CheckReportFolder declared in the line above.

    Hope the above proves useful to others 🙂
    Toby

    Reply
  14. Prashant

    Hi Kelivin, I am new to Powershell. I am using the CSV file & added Client ID and Client secret for Dell. Script runs withing fraction of seconds and comes out. No errors and no report generated though $CreateHTMLReport is set to true.

    Reply
  15. Prashant

    Hi Kelvin, Thanks for the quick response 🙂 It was not working because of upper case and lower case letter in “line” in “Get-WarrantyCSV” function. I am using it for Dell servers and it is working perfectly fine now. Thanks for the script. Could you please help to add an alert in script saying if warranty is getting expired in less than 30 days then send an email?

    Reply
  16. Matt Cameron

    The Get-HPWarranty appears to work fine for certain/older serials. Debugging a little I see that $HPReq.Status is 301 for these ones that I couldn’t get to work. I suspect this is related to what the manual site does wherein it says it needs more information and asks for a product number as well.

    I need to play more but I saw that Chrome Dev Tools showed modelNum being used in the request. So I added another option field for the SKU/Product number which populates modelNumber in the JSON (which was null in the script). I got a proper response after that. More testing is needed but I though that might help someone.

    Reply
  17. Graham Pocta

    Just implemented this tonight. I am very impressed – excellent bit of scripting. Do you take payment/beer donations? I’ll be cancelling a certain service after today and would like you to reap some of the benefits of that.

    Two questions which I may look into myself if I can find the time to fiddle with the script:

    1. Have you looked into getting Apple warranty info at all?
    2. Have you looked into grabbing purchase date and syncing that back up as well?

    Reply
    1. Kelvin Tegelaar Post author

      1.) No, I haven’t really. I completely forgot the existed! 😉 I’m gonna see if I can add something.

      2.) Yes, there are some modifications regarding that on my github. 🙂

      Reply
  18. Tom

    Hi there,

    I’ve been reimplementing your script into c#, working on HP warranty search. When I run the powershell and my c# version, I do not get the json returned data but instead html containing the HP help section (seems to be a redirect). I was inspecting the network traffic and I noticed that the request contains a populated “captcha_token” field now which has a capatcha token generated by googles recaptcha service. Can you confirm it is still working please?

    Thanks for the script!

    Reply
      1. Tom

        Hi again,

        Thanks for the reply.

        So I got it working, but after a day I am now getting captcha token failure errors. Here’s the json:

        {“errorCode”:”UNEXPECTED_ERROR”,”errorReason”:”Captcha didnt match and failed for the user”,”constraintViolations”:null}

        You ever seen this before?

        Reply
  19. bandukia

    Hi,

    Is it possible to have the output in csv instead of html, and combined instead for separated files.

    Regards

    Reply
    1. Brennan

      Did you ever get it to send it to a nice csv file? I can get it to output to the csv file format but because they all have different client names they separate into different files (just as my HTML files do).

      serialnumber,vendor,client
      1234,HP,contoso
      1234,Dell,toiletpaperco
      1234,MS,contoso
      1234,Lenovo,johndoe inc

      My csv output ends up looking like this, which is almost fine, except when opened in excel it doesn’t display nicely.

      #TYPE System.Management.Automation.PSCustomObject
      “Serial”,”Warranty Product name”,”StartDate”,”EndDate”,”Warranty Status”,”Client”
      “A1B2C3D”,”C, NBD ONSITE
      C, NBD ONSITE”,”2010-06-21″,”2013-06-22″,”Expired”,”console”

      Reply
  20. Prashant

    Hi,

    Is it possible to include feature like sending an single email alert of systems whose warranty is getting expired in 30 days in script?

    Thanks,
    Prashant

    Reply
  21. Martin

    kelvin you are awesome 😀
    we will use this scipt and run it in azure automation as a runbook. this will check all our intune devices and push the warranty data to our cmdb.

    Reply
      1. Martin

        Just got my Dell API Keys… all APIs are working fine now…. except HP.
        So far I was not able to get it working.

        We query the warranty data for devices which are not yet updated in the cmdb. So from time to time the amount of warranty informations will grow. If one query failes its not an issue.

        Reply
  22. Niv Dolgin

    This is brilliant. Thank you. Any plans to make Intune (or, the EndPoint Manager Graph API data) a source?

    Reply
  23. Marc C

    Hey!

    Thanks for sharing this! I’m a bit new to scripting and have been trying to read over the code to learn what I can. Before implementing this into my environment, will the “Warranty Product Name” be filled with the device hostname, or is this a generic field that will populate the device name (eg. Dell OptiPlex 3070)?

    Reply
    1. Kelvin Tegelaar Post author

      It will fill it with the Warranty Product name the vendor supplies, so for Dell that could be “Dell ProSupport 3 years” or HP “HP Carepaq”. hope that helps 🙂

      Reply
  24. Brennan

    First of all I think this is absolutely brilliant. I needed a way to show if our surface’s are in warranty or not since Kaseya doesn’t do that for us.

    I did find a slight problem when running my own machine. It shows the warranty status as expired but shows the warranty date as being good until 2021. Technically, yes, the original 1 year warranty has expired, but because of the additional warranty it is under warranty. (so warranty status would not be expired.)

    Any ideas on how to fix this would be greatly appreciated.

    Reply
  25. Shaun K

    Hi Kelvin,

    You are going Gods work here!!

    Just with Datto RMM as tye have force MFA onto all users now so the script fails to connect with basic auth. Is it possible to change to connect using the API Key and API secret ?

    Reply
  26. James Croxford

    Is anyone getting the following error when running the script for Autotask?

    Import-Module : IntegrationCode is invalid.
    Stacktrace:
    at Get-AtwsData, C:\Program Files\WindowsPowerShell\Modules\Autotask\1.6.9\Private\Get-AtwsData.ps1: line 117
    at Connect-AtwsWebServices, C:\Program Files\WindowsPowerShell\Modules\Autotask\1.6.9\Private\Connect-AtwsWebServices.ps1: line 215
    at , C:\Program Files\WindowsPowerShell\Modules\Autotask\1.6.9\Autotask.psm1: line 134

    Reply
  27. Mark

    This is excellent! Thanks Kelvin, much appreciated!

    A few things I’d like to know.

    – Is there any way to exclude duplicates in the Warranty Product Name? I see the same product names listed twice for the same machine i.e. “ND ProSupport EndUser On-Site Extended with Dates”
    – Is it possible to include some sort of count down til the warranty end date?

    Regards

    Reply
  28. Owen

    Is it just me or is the HP functions till broken?
    I keep getting 500 errors, I did some snooping and found their website has an “api_version” tag in the body which I added and this got past the 500 error but then returns null objects so it just as helpful.

    Reply
  29. James Croxford

    Is there anyway to embed the Username and Password for the AutoTask API account into scirpt?

    Also can you get Apple warranty information?

    Reply
  30. Martin Tylich

    Hi please I have problem. I am getting this:
    Invoke-RESTMethod : {
    “error”:”invalid_client”,
    “error_description”:”The given client credentials were not valid”
    }
    At C:\Users\MTylich\Downloads\PowerShellWarrantyReports-master\PowerShellWarrantyReports-master\WarrantyRetrieval.ps1:6
    3 char:23
    + … uthResult = Invoke-RESTMethod -Method Post -Uri $AuthURI -Body $AuthB …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-RestMethod], WebExc
    eption
    + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand
    Invoke-RestMethod : {

    “Fault”: {
    “faultcode”: “401”,
    “faultstring”: “Invalid Authentication”,
    “faultactor”: “https://apigtwb2c.us.dell.com/PROD/sbil/eapi/v5/asset-entitlements?servicetags=3TF3VN2”
    }

    }
    At C:\Users\MTylich\Downloads\PowerShellWarrantyReports-master\PowerShellWarrantyReports-master\WarrantyRetrieval.ps1:7
    0 char:15
    + … $WarReq = Invoke-RestMethod -Uri “https://apigtwb2c.us.dell.com/PRO …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-RestMethod], WebExc
    eption
    + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand

    I am sure that API key and secure is correct so I don’t know what is wrong.

    Thanks

    Reply
  31. Colin Barr

    Thanks so much for posting this!

    I’m trying to do this with IT Glue. I have generated an API key in ITG and have put it after the -ITGlueAPIKey switch, but when I run it, it seems to prompt for credentials, and no matter what I put in, it retrieves no data with no errors.

    Just wondering if you could hint at what I am doing wrong?

    This is the syntax I am using (with the API Key replaced)

    update-warrantyinfo -ITGue -ITGlueAPIKey xxMadeUpAPIKeyxxx -GenerateReports -returnwarrantyobject -ReportsLocation c:\script\warr

    Reply
    1. Colin Barr

      sorry, I forgot the parameter for the URL. This is the command I am using:

      update-warrantyinfo -ITGue ITGlueAPIURL https://api.itglue.com -ITGlueAPIKey xxMadeUpAPIKeyxxx -GenerateReports -returnwarrantyobject -ReportsLocation c:\script\warr

      Reply
      1. Colin Barr

        Argh! This is the command with the typos fixed:

        update-warrantyinfo -ITGue -ITGlueAPIURL https://api.itglue.com -ITGlueAPIKey xxMadeUpAPIKeyxxx -GenerateReports -returnwarrantyobject -ReportsLocation c:\script\warr

        Sorry

        Reply
        1. Kelvin Tegelaar Post author

          It seems that the PSGallery had a rollback and one of the updates has not been registered. Version 0.7.1 is has the fixes for these issues.

          I’ll try to reupload today, but the PSGallery is having some issues. If you want, you can download the module from github instead.

          Reply
  32. Marcel B

    Somehow when running this script i’m getting a lot of errors. This parameters where used (error below):

    $source = “AT” #AT, CSV, ITG, CW
    ##### Sync Settings
    $SyncWithSource = $false #Sync status warranty dates/status back to PSA/Management system. Only works with dynamic sources like ITG and AT.
    $OverwriteWarranty = $false #Overwrites the date already found in AT with the one based on this API, unless the API could not find information.
    $CreateHTMLReport = $true #Creates an HTML report.
    ###### File locations
    $ReportsLocation = “C:\temp\reports” #Only required if Reporting is enabled.
    $sourcefile = “C:\temp\temp.csv” #only required if source is not autotask.
    $ATLogPath = “C:\temp\AT.txt” #Only used to log which objects have been synced with AT as AT does not have a audit log.
    ##### AT API Settings
    $ATAPIKey = “key@domain.nl” #only required if source is Autotask.

    — errors —
    At C:\Marcel\Warranty.ps1:365 char:95
    + … splay = […tr.children].find(td => td.innerHTML.toUpperCase().includ …
    + ~
    An expression was expected after ‘(‘.
    At C:\Marcel\Warranty.ps1:365 char:106
    + … .tr.children].find(td => td.innerHTML.toUpperCase().includes(filter)) …
    + ~
    Missing ‘)’ in method call.
    At C:\Marcel\Warranty.ps1:362 char:23
    + function myFunction() {
    + ~
    Missing closing ‘}’ in statement block or type definition.
    At C:\Marcel\Warranty.ps1:309 char:8
    + do {
    + ~
    Missing closing ‘}’ in statement block or type definition.
    At C:\Marcel\Warranty.ps1:365 char:128
    + … nd(td => td.innerHTML.toUpperCase().includes(filter)) ? ” : ‘none’);
    + ~
    Missing while or until keyword in do loop.
    At C:\Marcel\Warranty.ps1:298 char:28
    + function Get-WarrantyCW() {
    + ~
    Missing closing ‘}’ in statement block or type definition.
    At C:\Marcel\Warranty.ps1:365 char:128
    + … nd(td => td.innerHTML.toUpperCase().includes(filter)) ? ” : ‘none’);
    + ~
    Unexpected token ‘)’ in expression or statement.
    At C:\Marcel\Warranty.ps1:366 char:3
    + }
    + ~
    Unexpected token ‘}’ in expression or statement.
    At C:\Marcel\Warranty.ps1:372 char:3
    + td, th { border:0px solid black;
    + ~
    Missing argument in parameter list.
    At C:\Marcel\Warranty.ps1:377 char:6
    + table, tr, td, th {
    + ~
    Missing argument in parameter list.
    Not all parse errors were reported. Correct the reported errors and try again.
    + CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : ExpectedExpression

    Reply
      1. Marcel B

        Hi Kelin,

        I did same results. Now I have loaded 2 modules from the library.

        install-module AutotaskAPI -Force
        install-module PSWarranty -Force

        Ran it and when the credentials where asked. Do I need to enter my own credentials? I use 2FA in AT. This never works I think. I get this….

        update-warrantyinfo -Autotask -AutotaskCredentials $creds -AutotaskAPIKey ‘fykxwunce5nwfyf@EXPERIT.NL’ -GenerateReports
        cmdlet Get-Credential at command pipeline position 1
        Supply values for the following parameters:
        [Autotask, True] [AutotaskCredentials, System.Management.Automation.PSCredential] [AutotaskAPIKey, fykxwunce5nwfyf@EXPERIT.NL] [GenerateReports, True]
        Retrieving webservices URI based on username
        Setting AutotaskBaseURI to https://webservices4.autotask.net/ATServicesRest/ using version V1.0
        Setting API resource parameters. This may take a moment.
        Logging into Autotask. Grabbing all client information.
        Get-AutotaskAPIResource : Connecting to the Autotask API failed. De externe server heeft een fout geretourneerd: (500) Interne serverfout.
        At C:\Program Files\WindowsPowerShell\Modules\PSWarranty\0.7\private\Get-WarrantyAutotask.ps1:13 char:19
        + … llClients = Get-AutotaskAPIResource -resource Companies -SimpleSearch …
        + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
        + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Get-AutotaskAPIResource

        Client information found. Grabbing all devices
        Get-AutotaskAPIResource : Connecting to the Autotask API failed. De externe server heeft een fout geretourneerd: (500) Interne serverfout.
        At C:\Program Files\WindowsPowerShell\Modules\PSWarranty\0.7\private\Get-WarrantyAutotask.ps1:15 char:19
        + … llDevices = Get-AutotaskAPIResource -resource ConfigurationItems -Sim …
        + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
        + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Get-AutotaskAPIResource

        Collecting information. This can take a long time.
        Done collecting warranty information. Generating reports.

        Reply
        1. Kelvin Tegelaar Post author

          You’ll need the Autotask API user integration code, username, and password. As that’s currently returning error 500 you most likely are not using the correct credentials. MFA does not matter if you configured the API correctly.

          Reply
          1. Marcel B

            Hi Kelvin,

            Is there a howto discription somewhere how to create that API user. No matter what creds I use I’ll keep bumping into the 500 error. And also what fields do I need to enter where?

            Is the field called AutotaskAPIKey in the script the Username(key) in Autotask? And within the popup, what is the username and password when looking into the API user in Autotask? I have 4 fields, Username(key, Secret, Internal Integration Name and Tracking Identifyer. Where do I map them wihtin the script and popup?

            Sorry for all the questions.

            Thanks (dank!)

          2. JRS

            I had the same problem. The AutoTask API Key in the command needs to be the Tracking Identifier from the Autotask API user account (you have to set up a new API user account and mark it as a custom integration). Then use the API User account username and password for the credentials.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.