public/psf-devices.ps1

function Find-FalconDuplicate {
<#
.SYNOPSIS
Find potential duplicate hosts within your Falcon environment
.DESCRIPTION
If the 'Hosts' parameter is not provided, all Host information will be retrieved. An error will be
displayed if required fields 'cid', 'device_id', 'first_seen', 'last_seen', 'hostname' and any defined
'filter' value are not present.
 
Hosts are grouped by 'cid', 'hostname' and any defined 'filter' values, then sorted by 'last_seen' time. Any
result other than the one with the most recent 'last_seen' time is considered a duplicate host and is returned
within the output.
 
Hosts can be hidden from the Falcon console by piping the results of 'Find-FalconDuplicate' to
'Invoke-FalconHostAction' using the action 'hide_host'.
 
Requires 'Hosts: Read'.
.PARAMETER Hosts
Array of detailed Host results
.PARAMETER Filter
Property to determine duplicates, in addition to 'Hostname'
.PARAMETER Platform
Filter hosts by platform
.LINK
https://github.com/crowdstrike/psfalcon/wiki/Find-FalconDuplicate
#>

  [CmdletBinding(SupportsShouldProcess)]
  param(
    [Parameter(Position=1)]
    [object[]]$Hosts,
    [Parameter(Position=2)]
    [ValidateSet('external_ip','local_ip','mac_address','os_version','platform_name','serial_number',
      IgnoreCase=$false)]
    [string[]]$Filter,
    [Parameter(Position=3)]
    [ValidateSet('Linux','Mac','Windows',IgnoreCase=$false)]
    [string]$Platform
  )
  begin {
    function Group-Selection ($Object,$GroupBy) {
      ($Object | Group-Object $GroupBy).Where({$_.Count -gt 1 -and $_.Name}).foreach{
        $_.Group | Sort-Object last_seen | Select-Object -First ($_.Count - 1)
      }
    }
    # Comparison criteria and required properties for host results
    [string[]]$Criteria = 'cid','hostname'
    [string[]]$Required = 'cid','device_id','first_seen','last_seen','hostname'
    if ($PSBoundParameters.Filter) {
      $Criteria = $Criteria + $PSBoundParameters.Filter
      $Required = $Required + $PSBoundParameters.Filter
    }
    # Create filter for excluding results with empty $Criteria values
    $FilterScript = "$(($Criteria).foreach{ "`$_.$($_)" } -join ' -and ')"
  }
  process {
    if ($PSCmdlet.ShouldProcess('Find-FalconDuplicate','Get-FalconHost')) {
      [object[]]$HostArray = if (!$PSBoundParameters.Hosts) {
        # Retrieve Host details
        $Param = @{
          Detailed = $true
          All = $true
          ErrorAction = 'SilentlyContinue'
        }
        if ($PSBoundParameters.Platform) {
          $Param['Filter'] = "platform_name:'$($PSBoundParameters.Platform)'"
        }
        Get-FalconHost @Param
      } else {
        $PSBoundParameters.Hosts
      }
      if ($HostArray) {
        @($Required).foreach{
          if (($HostArray | Get-Member -MemberType NoteProperty).Name -notcontains $_) {
            # Verify required properties are present
            throw "Missing required property '$_'."
          }
        }
        $Param = @{
          # Group, sort and output result
          Object = $HostArray | Select-Object $Required | Where-Object -FilterScript {$FilterScript}
          GroupBy = $Criteria
        }
        $Output = Group-Selection @Param
        if ($Output) {
          $Output
        } else {
          $PSCmdlet.WriteWarning("[Find-FalconDuplicate] No duplicates found.")
        }
      }
    }
  }
}
function Find-FalconHostname {
<#
.SYNOPSIS
Find hosts using a list of hostnames
.DESCRIPTION
Perform hostname searches in groups of 100.
 
Requires 'Hosts: Read'.
.PARAMETER InputObject
One or more hostnames to find
.PARAMETER Path
Path to a plain text file containing hostnames
.PARAMETER Include
Include additional properties
.PARAMETER Partial
Perform a non-exact search
.LINK
https://github.com/crowdstrike/psfalcon/wiki/Find-FalconHostname
#>

  [CmdletBinding(DefaultParameterSetName='Path',SupportsShouldProcess)]
  param(
    [Parameter(ParameterSetName='Pipeline',Mandatory,ValueFromPipeline)]
    [Alias('Array')]
    [string[]]$InputObject,
    [Parameter(ParameterSetName='Path',Mandatory,Position=1)]
    [ValidateScript({
      if (Test-Path $_ -PathType Leaf) {
        $true
      } else {
        throw "Cannot find path '$_' because it does not exist."
      }
    })]
    [string]$Path,
    [Parameter(ParameterSetName='Path',Position=2)]
    [Parameter(ParameterSetName='Pipeline',Position=2)]
    [ValidateSet('agent_version','cid','external_ip','first_seen','hostname','last_seen','local_ip','mac_address',
      'os_build','os_version','platform_name','product_type','product_type_desc','serial_number',
      'system_manufacturer','system_product_name','tags',IgnoreCase=$false)]
    [string[]]$Include,
    [Parameter(ParameterSetName='Path')]
    [Parameter(ParameterSetName='Pipeline')]
    [switch]$Partial
  )
  begin {
    [System.Collections.Generic.List[string]]$List = @()
    if ($Path) { [string]$Path = $Script:Falcon.Api.Path($Path) }
    [string[]]$Select = 'device_id','hostname'
    if ($Include) { $Select += $Include }
  }
  process {
    if ($Path) {
      $List.AddRange([string[]]((Get-Content -Path $Path).Normalize()).Where({
        ![string]::IsNullOrWhiteSpace($_)}))
    } elseif ($InputObject) {
      @($InputObject).Where({![string]::IsNullOrWhiteSpace($_)}).foreach{ $List.Add($_) }
    }
  }
  end {
    if ($List) {
      $List = @($List) | Select-Object -Unique
      for ($i=0; $i -lt ($List | Measure-Object).Count; $i+=100) {
        [string[]]$Group = @($List)[$i..($i+99)]
        [string]$Filter = if ($Partial) {
          (@($Group).foreach{ "hostname:'$_'" }) -join ','
        } else {
          (@($Group).foreach{ "hostname:['$_']" }) -join ','
        }
        $Req = Get-FalconHost -Filter $Filter -Detailed | Select-Object $Select
        @($Group).foreach{
          if (($Partial -and $Req.hostname -notlike "$_*") -or (!$Partial -and $Req.hostname -notcontains $_)) {
            $PSCmdlet.WriteWarning("[Find-FalconHostname] No match found for '$_'.")
          }
        }
        if ($Req) { $Req }
      }
    }
  }
}