Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
238 changes: 238 additions & 0 deletions PowerShell Scanners/Get-CDPLLDP/Get-CDPLLDP.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
<#
.SYNOPSIS
Ultra-silent pktmon CDP/LLDP discovery with
cleaned neighbor/port strings.
#>
[CmdletBinding()]
param (
[int]$MaxAttempts = 10, #As a PDQ Connect scanner, it will time out after 2 min, change this to 2 but you might not capture the elusive packets in that time
[int]$CaptureSecondsPerAttempt = 30, #Alternatively run as an automated package or scheduled task
[switch]$IncludeLLDP = $true,
[switch]$ClearCache
)

# Admin check
if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
return [PSCustomObject]@{
Date = (Get-Date).ToUniversalTime().ToString("yyyy-MM-dd HH:mm:ssZ")
NeighborDeviceName = "ERROR: Administrator privileges required"
NeighborDevicePort = "ERROR: Administrator privileges required"
NeighborDevicePortDesc = "ERROR: Administrator privileges required"
Protocol = "N/A"
}
}

$CacheFile = Join-Path $env:TEMP -ChildPath "CDP_LLDPCache.json"

# Load cache
if ((Test-Path $CacheFile) -and -not $ClearCache) {
try {
$CachedRecords = Get-Content $CacheFile -Raw | ConvertFrom-Json
# Flatten incoming cached objects to ensure dates are plain strings
if ($CachedRecords) {
$CachedRecords = @($CachedRecords) | ForEach-Object {
$CleanDate = if ($_.Date -like '@{value=*') { (Get-Date).ToUniversalTime().ToString("yyyy-MM-dd HH:mm:ssZ") } else { [string]$_.Date }
[PSCustomObject]@{
Date = $CleanDate
NeighborDeviceName = [string]$_.NeighborDeviceName
NeighborDevicePort = [string]$_.NeighborDevicePort
NeighborDevicePortDesc = [string]$_.NeighborDevicePortDesc
Protocol = [string]$_.Protocol
}
}
}
} catch {
$CachedRecords = @()
}
} else {
$CachedRecords = @()
}

# Check Ethernet
$EthernetUp = Get-NetAdapter -Physical -ErrorAction SilentlyContinue |
Where-Object { $_.Status -eq 'Up' -and $_.MediaType -eq '802.3' }

if (-not $EthernetUp) {
if ($CachedRecords.Count -eq 0) {
return [PSCustomObject]@{
Date = (Get-Date).ToUniversalTime().ToString("yyyy-MM-dd HH:mm:ssZ")
NeighborDeviceName = "No neighbor found"
NeighborDevicePort = "No neighbor found"
NeighborDevicePortDesc = "No neighbor found"
Protocol = "N/A"
}
} else {
return $CachedRecords
}
}

# === Ethernet connected - capture ===
$Seen = @{}
$NewResults = @()

foreach ($item in $CachedRecords) {
$Key = "$($item.NeighborDeviceName)|$($item.NeighborDevicePort)"
if (-not $Seen.ContainsKey($Key)) {
$Seen[$Key] = $true
}
}

for ($Attempt = 1; $Attempt -le $MaxAttempts; $Attempt++) {
$Timestamp = Get-Date -Format 'yyyyMMdd_HHmmss'
$EtlFile = Join-Path $env:TEMP -ChildPath "Discovery_${Timestamp}.etl"
$TxtFile = $EtlFile -replace '\.etl$', '.txt'

try {
pktmon filter remove *> $null 2>&1
pktmon filter add -m 01-00-0C-CC-CC-CC *> $null 2>&1
if ($IncludeLLDP) {
pktmon filter add -d 0x88cc *> $null 2>&1
}

pktmon start --capture --pkt-size 0 --file-name $EtlFile --comp nics *> $null 2>&1
Start-Sleep -Seconds $CaptureSecondsPerAttempt
pktmon stop *> $null 2>&1

pktmon etl2txt $EtlFile -o $TxtFile --verbose 3 *> $null 2>&1

$Content = Get-Content $TxtFile -Raw -ErrorAction SilentlyContinue
if (-not $Content) { continue }

<#
# =========================================================================
# DEBUG BLOCK: Output human-readable packet text windows containing data
# =========================================================================
$DebugSections = $Content -split '(?i)\[\d+\]\d{4}\.\d+::'
foreach ($DSection in $DebugSections) {
if ($DSection -match 'Device-ID|Chassis ID|System Name|Port-ID|Port ID') {
Write-Host "`n" -NoNewline
Write-Host "==================== [RAW DISCOVERY PACKET FRAME] ====================" -ForegroundColor Cyan
Write-Host $DSection.Trim() -ForegroundColor White
Write-Host "======================================================================" -ForegroundColor Cyan
Write-Host "`n" -NoNewline
}
}
# =========================================================================
#>

$Sections = $Content -split '(?i)\[\d+\]\d{4}\.\d+::'

foreach ($Section in $Sections) {
if ($Section -match 'Device-ID|Chassis ID|System Name|Port-ID|Port ID') {

$Device = ""
$Port = ""
$PortDesc = ""

# Read line-by-line to avoid multi-line regex matching jumps
$Lines = $Section -split '\r?\n'
for ($i = 0; $i -lt $Lines.Count; $i++) {
$Line = $Lines[$i]

# 1. Capture Device Name
if ($Line -match '(?i)(?:Device-ID|System Name TLV)') {
if ($Line -match ':\s*(.*)$' -and $Matches[1] -and $Matches[1] -notmatch 'length') {
$Device = $Matches[1]
} elseif ($i + 1 -lt $Lines.Count -and $Lines[$i+1] -match ':\s*(.*)$') {
$Device = $Matches[1]
}
}

# 2. Capture Port ID
if ($Line -match '(?i)(?:Port-ID|Port ID TLV)') {
if ($Line -match ':\s*(.*)$' -and $Matches[1] -and $Matches[1] -notmatch 'length') {
$Port = $Matches[1]
} elseif ($i + 1 -lt $Lines.Count -and $Lines[$i+1] -match ':\s*(.*)$') {
$Port = $Matches[1]
}
}

# 3. Capture Port Description
if ($Line -match '(?i)Port Description TLV') {
if ($Line -match ':\s*(.*)$') {
$PortDesc = $Matches[1]
}
}
}

# Fallback safely: if explicit Port ID isn't found, try Port Description
if ([string]::IsNullOrWhiteSpace($Port)) { $Port = $PortDesc }

# Strip trailing line data artifacts
$Device = $Device -replace '\s*(?:value length|bytes|TLV|Subtype|\(|,).*$', '' -replace '\s*0x.*$', ''
$Port = $Port -replace '\s*(?:value length|bytes|TLV|Subtype|\(|,).*$', '' -replace '\s*0x.*$', ''
$PortDesc = $PortDesc -replace '\s*(?:value length|bytes|TLV|Subtype|\(|,).*$', '' -replace '\s*0x.*$', ''

# Dynamic clean array to strip out wrapping spaces, quotes, and carriage returns
$TrimChars = @(' ', "'", '"', '`', '(', ')', "`r")
$Device = $Device.Trim($TrimChars)
$Port = $Port.Trim($TrimChars)
$PortDesc = $PortDesc.Trim($TrimChars)

# Skip if the text output parses down to generic metadata tags or empty lines
if ([string]::IsNullOrWhiteSpace($Device) -or [string]::IsNullOrWhiteSpace($Port) -or $Device -ieq "TLV" -or $Port -ieq "TLV" -or $Device -match '^\d+$') {
continue
}

$Key = "$Device|$Port"
if (-not $Seen.ContainsKey($Key) -and $Device.Length -gt 1 -and $Port.Length -gt 1) {
$Seen[$Key] = $true

$NewResults += [PSCustomObject]@{
Date = (Get-Date).ToUniversalTime().ToString("yyyy-MM-dd HH:mm:ssZ")
NeighborDeviceName = $Device
NeighborDevicePort = $Port
NeighborDevicePortDesc = $PortDesc
Protocol = if ($Section -match '0x88cc|LLDP') { "LLDP" } else { "CDP" }
}
}
}
}

if ($NewResults.Count -gt 0) {
break
}
}
finally {
@($EtlFile, $TxtFile) | ForEach-Object {
if (Test-Path $_) { Remove-Item $_ -Force -ErrorAction SilentlyContinue }
}
}

if ($Attempt -lt $MaxAttempts) {
Start-Sleep -Seconds 5
}
}

# Merge arrays together
$AllRecords = $NewResults + $CachedRecords

# Force older deserialized date objects to fall flat into pure strings during formatting pipeline
$FinalRecords = $AllRecords | ForEach-Object {
[PSCustomObject]@{
Date = [string]($_.Date)
NeighborDeviceName = [string]($_.NeighborDeviceName)
NeighborDevicePort = [string]($_.NeighborDevicePort)
NeighborDevicePortDesc = [string]($_.NeighborDevicePortDesc)
Protocol = [string]($_.Protocol)
}
} | Select-Object -First 5

# Save cache
$FinalRecords | ConvertTo-Json -Depth 3 | Set-Content $CacheFile -Force

# Cleanup pktmon filters before exiting
pktmon filter remove *> $null 2>&1

# Return Results
if ($FinalRecords.Count -eq 0) {
return [PSCustomObject]@{
Date = (Get-Date).ToUniversalTime().ToString("yyyy-MM-dd HH:mm:ssZ")
NeighborDeviceName = "No neighbor found"
NeighborDevicePort = "No neighbor found"
NeighborDevicePortDesc = "No neighbor found"
Protocol = "N/A"
}
} else {
return $FinalRecords
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
$tempPath = $env:TEMP
$TagFile = Join-Path -Path $tempPath -ChildPath "servicetag.csv"
$WarrantyFile = Join-Path -Path $tempPath -ChildPath "warranty.csv"
$cliPath = "C:\Program Files (x86)\Dell\CommandIntegrationSuite\DellWarranty-CLI.exe" #https://www.dell.com/support/kbdoc/en-us/000146749/dell-command-warranty
#to install silent the exe switch is /Q /v/qn

$CacheDays = 30

# 1. Get Service Tag
$ServiceTag = (Get-CimInstance Win32_BIOS).SerialNumber

# 2. Determine if we need to run a fresh scan
$NeedsUpdate = $true
if (Test-Path $WarrantyFile) {
$FileAge = (Get-Date) - (Get-Item $WarrantyFile).LastWriteTime
if ($FileAge.Days -lt $CacheDays) {
$NeedsUpdate = $false
}
}

# 3. Run the EXE only if needed
if ($NeedsUpdate) {
if (Test-Path $cliPath) {
$ServiceTag | Out-File -FilePath $TagFile -Encoding ascii
& $cliPath /I="$TagFile" /E="$WarrantyFile" *> $null
if (Test-Path $TagFile) { Remove-Item $TagFile -Force }
}
else {
return [PSCustomObject]@{
"Service Tag" = $ServiceTag
"Warranty Start Date" = $null
"Warranty End Date" = $null
"Status" = "CLI Tool Not Installed"
}
}
}

# 4. Import and Parse the (New or Cached) file
if ((Test-Path $WarrantyFile) -and (Get-Item $WarrantyFile).Length -gt 0) {
$rawCSV = Import-Csv -Path $WarrantyFile

$latestStart = ($rawCSV | ForEach-Object { [datetime]$_."Start Date" } | Sort-Object -Descending | Select-Object -First 1)
$latestEnd = ($rawCSV | ForEach-Object { [datetime]$_."End Date" } | Sort-Object -Descending | Select-Object -First 1)

$Result = [PSCustomObject]@{
"Service Tag" = $ServiceTag
"Warranty Start Date" = $latestStart.ToString("yyyy-MM-dd")
"Warranty End Date" = $latestEnd.ToString("yyyy-MM-dd")
"Status" = if ($NeedsUpdate) { "Fresh Scan" } else { "Cached (Age: $($FileAge.Days) days)" }
}
}
else {
$Result = [PSCustomObject]@{
"Service Tag" = $ServiceTag
"Warranty Start Date" = $null
"Warranty End Date" = $null
"Status" = "No Warranty Data Found"
}
}

return $Result
22 changes: 22 additions & 0 deletions PowerShell Scanners/Get-Dell-Warranty-CMD/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Get-Dell-Warranty-CMD

## Instructions

Install Dell Command | Integration Suite for System Center, dont worry its not just for system center, it installs as a normal Dell command line tool like all the others.

At time of writing:
https://www.dell.com/support/kbdoc/en-us/000146749/dell-command-warranty

## Description

Get Dell warranty start and end date using Dell command line tool hence "CMD" versus "API"
There is an API based tool available for PDQ Inventory on GitHub https://github.com/nichols-eric/KUDellWarrantyChecker

## Compatibility

* **PDQ Inventory**: No
* **PDQ Connect**: Yes

## Author

Eric Nichols
29 changes: 29 additions & 0 deletions PowerShell Scanners/Get-Dell-Warranty-CMD/Scan Profile.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<AdminArsenal.Export Code="PDQInventory" Name="PDQ Inventory" Version="19.3.30.0" MinimumVersion="19.0">
<ScanProfile>
<Collections type="list" />
<Scanners type="list">
<Scanner>
<ModifiedDate>2026-05-28T22:49:37.8428150-07:00</ModifiedDate>
<Name>Get-Dell-Warranty-CMD</Name>
<UID>20ff84d0445c46b2960380a273afe89b</UID>
<Script>
</Script>
<FileName>C:\PowerShell-Scanners\PowerShell Scanners\Get-Dell-Warranty-CMD\Get-Dell-Warranty-CMD.ps1</FileName>
<Parameters>
</Parameters>
<AdditionalFiles>
</AdditionalFiles>
<RowLimit value="100" />
<TypeName>PowerShell</TypeName>
<SourceScannerId value="90" />
</Scanner>
</Scanners>
<Description>Get Dell warranty start and end date using Dell command line tool hence "CMD" versus "API"</Description>
<ScanProfileId value="33" />
<Name>PS - Get-Dell-Warranty-CMD</Name>
<ScheduleTriggerSet name="ScheduleTriggers">
<Triggers type="list" />
</ScheduleTriggerSet>
</ScanProfile>
</AdminArsenal.Export>