<# .SYNOPSIS Counts devices per location (or label, or any column) from a KACE SMA inventory CSV export. .DESCRIPTION Reads a device inventory CSV exported from the Quest KACE SMA console and groups it by the column you choose. Optionally keeps only devices created or last seen within the last N days. Column names differ depending on which columns you had showing when you exported, so every column is a parameter. If one isn't found, the script lists the columns that are there. .PARAMETER Path The CSV file exported from KACE. .PARAMETER GroupBy The column to group on, for example Location or Labels. Default: Location. .PARAMETER DateColumn A date column to filter on, for example Created. Only used with -Days. .PARAMETER Days Keep only rows whose DateColumn falls in the last N days. .PARAMETER Delimiter If the GroupBy column holds several values (labels usually do), split on this character. .PARAMETER IncludeBlank Count devices with an empty GroupBy value as '(none)' instead of dropping them. .EXAMPLE .\Get-KaceLocationReport.ps1 -Path .\devices.csv -DateColumn Created -Days 7 .EXAMPLE .\Get-KaceLocationReport.ps1 -Path .\devices.csv -GroupBy Labels -Delimiter ',' #> [CmdletBinding()] param( [Parameter(Mandatory)][ValidateScript({ Test-Path -LiteralPath $_ -PathType Leaf })][string]$Path, [string]$GroupBy = 'Location', [string]$DateColumn, [ValidateRange(1, 3650)][int]$Days, [string]$Delimiter, [switch]$IncludeBlank ) $ErrorActionPreference = 'Stop' $rows = @(Import-Csv -LiteralPath $Path) if ($rows.Count -eq 0) { throw "'$Path' has no data rows." } $columns = $rows[0].PSObject.Properties.Name $needed = @($GroupBy) + @(if ($Days) { $DateColumn }) foreach ($col in $needed) { if (-not $col) { throw '-Days needs -DateColumn too, so the script knows which date to check.' } if ($columns -notcontains $col) { throw "Column '$col' isn't in this export. Columns found: $($columns -join ', ')" } } if ($Days) { $cutoff = (Get-Date).AddDays(-$Days) $skipped = 0 $rows = foreach ($row in $rows) { $when = [datetime]::MinValue if ([datetime]::TryParse($row.$DateColumn, [ref]$when)) { if ($when -ge $cutoff) { $row } } else { $skipped++ } } $rows = @($rows) if ($skipped) { Write-Warning "$skipped row(s) had an empty or unreadable '$DateColumn' value and were left out." } Write-Verbose "$($rows.Count) device(s) with $DateColumn in the last $Days day(s)." } # Turn each device into one or more group names. $values = foreach ($row in $rows) { $raw = [string]$row.$GroupBy $parts = if ($Delimiter) { $raw -split [regex]::Escape($Delimiter) } else { , $raw } $parts = @($parts | ForEach-Object { $_.Trim() } | Where-Object { $_ -and $_ -ne 'unassigned' }) if ($parts.Count) { $parts } elseif ($IncludeBlank) { '(none)' } } $total = $rows.Count $values | Group-Object | Sort-Object @{ Expression = 'Count'; Descending = $true }, Name | ForEach-Object { [pscustomobject]@{ $GroupBy = $_.Name Devices = $_.Count PercentAll = if ($total) { [math]::Round(100 * $_.Count / $total, 1) } else { 0 } } }