public/psf-sensors.ps1

function Invoke-TagScript {
  param(
    [Parameter(Mandatory,Position=1)]
    [object]$Object,
    [Parameter(Mandatory,Position=2)]
    [ValidateSet('Add','Remove','Set',IgnoreCase=$false)]
    [string]$Action,
    [Parameter(Mandatory,Position=3)]
    [boolean]$QueueOffline,
    [Parameter(Position=4)]
    [string[]]$String
  )
  process {
    # Gather existing tags, check for uninstall protection and define initial output
    [string]$Protection = $Object.device_policies.sensor_update.uninstall_protection
    $Output = [PSCustomObject]@{
      cid = $Object.cid
      device_id = $Object.device_id
      tags = @(@($Object.tags).Where({$_ -match 'SensorGroupingTags/'}) -replace 'SensorGroupingTags/',$null)
      offline_queued = $false
      session_id = $null
      cloud_request_id = $null
      status = if (@('Linux','Mac','Windows') -notcontains $Object.platform_name) {
        # Abort when 'platform_name' is unexpected
        'UNSUPPORTED_PLATFORM'
      } elseif ($Object.platform_name -ne 'Windows' -and $Protection -eq 'ENABLED') {
        # Abort when uninstall protection is enabled and platform_name is not 'Windows'
        'NO_TOKEN_SUPPORT_FOR_OS'
      }
    }
    if (!$Output.status) {
      [string[]]$TagInput = $String -replace 'SensorGroupingTags/',$null
      [string]$ScriptPath = Join-Path (Show-FalconModule).ModulePath 'script'
      [string]$TagString = if (($Action -eq 'Add' -and !$Output.tags) -or $Action -eq 'Set') {
        # Select all provided tags when using 'Set', or 'Add' and no tags exist
        ($TagInput | Select-Object -Unique) -join ','
      } elseif ($Action -eq 'Add') {
        # Select tag(s) to append
        [boolean[]]$Append = @($TagInput).foreach{ if ($Output.tags -notcontains $_) { $true } else { $false }}
        if ($Append -eq $true) { (@($Output.tags + $TagInput) | Select-Object -Unique) -join ',' }
      } elseif ($Action -eq 'Remove') {
        # Select tag(s) to remove
        [boolean[]]$Remove = @($TagInput).foreach{ if ($Output.tags -contains $_) { $true } else { $false }}
        if ($Remove -eq $true) { (@($Output.tags).Where({$TagInput -notcontains $_})) -join ',' }
      }
      $Output.status = if ($Action -eq 'Add' -and !$TagString) {
        # Abort when tag(s) to 'Add' are already present
        'TAG_PRESENT'
      } elseif ($Action -eq 'Remove' -and !$Output.tags) {
        # Abort when no tags are present to 'Remove'
        'NO_TAG_SET'
      } elseif ($Action -eq 'Remove' -and $TagInput -and !$TagString) {
        # Abort when tag(s) to 'Remove' are not present
        'TAG_NOT_PRESENT'
      } else {
        # Verify that device is currently online
        [string]$State = (Get-FalconHost -Id $Object.device_id -State).state
        if ($QueueOffline -eq $true -or ($QueueOffline -eq $false -and $State -eq 'online')) {
          # Add quotes around tag value string for Windows script use
          if ($TagString) { $TagString = ('"{0}"' -f $TagString) }
          [string]$CmdLine = if ($Protection -eq 'ENABLED') {
            # Retrieve uninstallation token and add to 'CommandLine' when host is 'online'
            [string]$Token = (Get-FalconUninstallToken -Id $Object.device_id -AuditMessage (($Action,
              'FalconSensorTag' -join '-'),"[$((Show-FalconModule).UserAgent)]" -join ' ')).uninstall_token
            if ($TagString) { $TagString,$Token -join ' ' } else { $Token }
          } elseif ($TagString) {
            $TagString
          }
          # Import script content and run script using Real-time Response
          [string]$ScriptName = if ($Action -eq 'Remove' -and !$TagString) {
            'remove_sensortag'
          } else {
            'add_sensortag'
          }
          [string]$Extension = switch ($Object.platform_name) {
            'Linux' { 'sh' }
            'Mac' { 'zsh' }
            'Windows' { 'ps1' }
          }
          [string]$ScriptFile = (Join-Path $ScriptPath ($ScriptName,$Extension -join '.'))
          if (!(Test-Path $ScriptFile)) { throw "Failed to locate '$ScriptFile'." }
          Write-Log ($Action,'FalconSensorTag' -join '-') "Importing '$ScriptFile'..."
          $ScriptContent = Get-Content $ScriptFile -Raw
          $Param = @{
            Command = 'runscript'
            Argument = '-Raw=```{0}```' -f $ScriptContent
            HostId = $Object.device_id
            QueueOffline = $QueueOffline
          }
          if ($CmdLine) { $Param.Argument += (' -CommandLine=```{0}```' -f $CmdLine) }
          @(Invoke-FalconRtr @Param).foreach{
            # Capture offline queue status, 'errors' or 'stderr' in 'status'
            if ($_.offline_queued -eq $true) {
              'PENDING_QUEUE'
            } elseif ($_.errors) {
              $_.errors
            } elseif ($_.stderr) {
              $_.stderr
            } elseif ($_.stdout) {
              # Compare 'stdout' with $TagInput and update 'status'
              $Result = ($_.stdout).Trim()
              $Output.tags = if ($Result -match 'Maintenance Token>') { ($TagString).Trim('"') } else { $Result }
              [string[]]$FinalValue = $Output.tags -split ','
              if ($Action -eq 'Remove') {
                [boolean[]]$Removed = @($Tag).foreach{
                  if ($FinalValue -contains $_) { $false } else { $true }
                }
                if ($Removed -ne $false) { 'TAG_REMOVED' } else { 'TAG_NOT_REMOVED' }
              } else {
                [boolean[]]$Set = @($Tag).foreach{
                  if ($FinalValue -contains $_) { $true } else { $false }
                }
                if ($Set -ne $false) {
                  if ($Action -eq 'Add') { 'TAG_ADDED' } else { 'TAG_SET' }
                } else {
                  if ($Action -eq 'Add') { 'TAG_NOT_ADDED' } else { 'TAG_NOT_SET' }
                }
              }
            }
            # Append Real-time response properties to initial output
            foreach ($i in @('offline_queued','session_id','cloud_request_id')) { $Output.$i = $_.$i }
          }
        } else {
          # Abort if host is offline and 'QueueOffline' is false
          'HOST_OFFLINE_AND_NOT_QUEUED'
        }
      }
    }
    $Output.tags = $Output.tags -join ','
    $Output
  }
}
function Add-FalconSensorTag {
<#
.SYNOPSIS
Use Real-time Response to add SensorGroupingTags to a host
.DESCRIPTION
Provided SensorGroupingTag values will be appended to any existing tags. If no new tag values are supplied, a list
of the current tags will be output for the target host. To overwrite existing values, use 'Set-FalconSensorTag'.

Requires 'Hosts: Read', 'Sensor update policies: Write', 'Real time response: Read', and
'Real time response (admin): Write'.
.PARAMETER Tag
SensorGroupingTag value ['SensorGroupingTags/<string>']
.PARAMETER QueueOffline
Add command request to the offline queue
.PARAMETER Id
Host identifier
.LINK
https://github.com/crowdstrike/psfalcon/wiki/Add-FalconSensorTag
#>

  [CmdletBinding(SupportsShouldProcess)]
  param(
    [Parameter(Mandatory,Position=1)]
    [ValidateScript({
      @($_).foreach{
        if ((Test-RegexValue $_) -eq 'tag') {
          $true
        } else {
          throw "Valid values include letters numbers, hyphens, unscores and forward slashes. ['$_']"
        }
      }
    })]
    [Alias('Tags')]
    [string[]]$Tag,
    [Parameter(Position=2)]
    [boolean]$QueueOffline,
    [Parameter(Mandatory,ValueFromPipelineByPropertyName,ValueFromPipeline,Position=3)]
    [ValidatePattern('^[a-fA-F0-9]{32}$')]
    [Alias('ids','device_id','host_ids','aid')]
    [string[]]$Id
  )
  begin { [System.Collections.Generic.List[string]]$List = @() }
  process { if ($Id) { @($Id).foreach{ $List.Add($_) }}}
  end {
    if ($List) {
      foreach ($i in @(Get-FalconHost -Id $List | Select-Object cid,device_id,platform_name,device_policies,
      tags)) {
        Invoke-TagScript $i 'Add' $QueueOffline $Tag
      }
    }
  }
}
function Get-FalconSensorTag {
<#
.SYNOPSIS
Display SensorGroupingTags assigned to hosts
.DESCRIPTION
Returns 'cid', 'device_id', and any SensorGroupingTags listed under 'tags' within a 'Get-FalconHost' result.

Requires 'Hosts: Read'.
.PARAMETER Id
Host identifier
.LINK
https://github.com/crowdstrike/psfalcon/wiki/Get-FalconSensorTag
#>

  [CmdletBinding(SupportsShouldProcess)]
  param(
    [Parameter(Mandatory,ValueFromPipelineByPropertyName,ValueFromPipeline,Position=1)]
    [ValidatePattern('^[a-fA-F0-9]{32}$')]
    [Alias('ids','device_id','host_ids','aid')]
    [string[]]$Id
  )
  begin { [System.Collections.Generic.List[string]]$List = @() }
  process { if ($Id) { @($Id).foreach{ $List.Add($_) }}}
  end {
    if ($List) {
      @(Get-FalconHost -Id $List | Select-Object cid,device_id,tags).foreach{
        [PSCustomObject]@{
          cid = $_.cid
          device_id = $_.device_id
          tags = @($_.tags).Where({$_ -match 'SensorGroupingTags/'}) -replace 'SensorGroupingTags/',
            $null -join ','
        }
      }
    }
  }
}
function Remove-FalconSensorTag {
<#
.SYNOPSIS
Use Real-time Response to remove SensorGroupingTags from a host
.DESCRIPTION
When provided, SensorGroupingTag values will be removed from list of existing tags and others will be left
unmodified. If no tags are provided, all existing tags will be removed.

Requires 'Hosts: Read', 'Sensor update policies: Write', 'Real time response: Read', and
'Real time response (admin): Write'.
.PARAMETER Tag
SensorGroupingTag value ['SensorGroupingTags/<string>']
.PARAMETER Id
Host identifier
.PARAMETER QueueOffline
Add command request to the offline queue
.LINK
https://github.com/crowdstrike/psfalcon/wiki/Remove-FalconSensorTag
#>

  [CmdletBinding(SupportsShouldProcess)]
  [OutputType([PSCustomObject])]
  param(
    [Parameter(Position=1)]
    [ValidateScript({
      @($_).foreach{
        if ((Test-RegexValue $_) -eq 'tag') {
          $true
        } else {
          throw "Valid values include letters, numbers, hyphens, unscores and forward slashes. ['$_']"
        }
      }
    })]
    [Alias('Tags')]
    [string[]]$Tag,
    [Parameter(Position=2)]
    [boolean]$QueueOffline,
    [Parameter(Mandatory,ValueFromPipelineByPropertyName,ValueFromPipeline,Position=3)]
    [ValidatePattern('^[a-fA-F0-9]{32}$')]
    [Alias('ids','device_id','host_ids','aid')]
    [string[]]$Id
  )
  begin { [System.Collections.Generic.List[string]]$List = @() }
  process { if ($Id) { @($Id).foreach{ $List.Add($_) }}}
  end {
    if ($List) {
      foreach ($i in @(Get-FalconHost -Id $List | Select-Object cid,device_id,platform_name,device_policies,
      tags)) {
        Invoke-TagScript $i 'Remove' $QueueOffline $Tag
      }
    }
  }
}
function Set-FalconSensorTag {
<#
.SYNOPSIS
Use Real-time Response to set SensorGroupingTags on a host
.DESCRIPTION
Provided SensorGroupingTag values will overwrite any existing tags. To append to existing values, use
'Add-FalconSensorTag'.

Requires 'Hosts: Read', 'Sensor update policies: Write', 'Real time response: Read', and
'Real time response (admin): Write'.
.PARAMETER Tag
SensorGroupingTag value ['FalconSensorTags/<string>']
.PARAMETER QueueOffline
Add command request to the offline queue
.PARAMETER Id
Host identifier
.LINK
https://github.com/crowdstrike/psfalcon/wiki/Set-FalconSensorTag
#>

  [CmdletBinding(SupportsShouldProcess)]
  [OutputType([PSCustomObject])]
  param(
    [Parameter(Mandatory,Position=1)]
    [ValidateScript({
      @($_).foreach{
        if ((Test-RegexValue $_) -eq 'tag') {
          $true
        } else {
          throw "Valid values include letters numbers, hyphens, unscores and forward slashes. ['$_']"
        }
      }
    })]
    [Alias('Tags')]
    [string[]]$Tag,
    [Parameter(Position=2)]
    [boolean]$QueueOffline,
    [Parameter(Mandatory,ValueFromPipelineByPropertyName,ValueFromPipeline,Position=3)]
    [ValidatePattern('^[a-fA-F0-9]{32}$')]
    [Alias('ids','device_id','host_ids','aid')]
    [string[]]$Id
  )
  begin { [System.Collections.Generic.List[string]]$List = @() }
  process { if ($Id) { @($Id).foreach{ $List.Add($_) }}}
  end {
    if ($List) {
      foreach ($i in @(Get-FalconHost -Id $List | Select-Object cid,device_id,platform_name,device_policies,
      tags)) {
        Invoke-TagScript $i 'Set' $QueueOffline $Tag
      }
    }
  }
}
function Uninstall-FalconSensor {
<#
.SYNOPSIS
Use Real-time Response to uninstall the Falcon sensor from a host
.DESCRIPTION
This command uses information from the registry and/or relevant Falcon command line utilities of the target host
to uninstall the Falcon sensor. If the sensor is damaged or malfunctioning, Real-time Response may not work
properly and/or the uninstallation may not succeed.

Requires 'Hosts: Read', 'Sensor update policies: Write', 'Real time response: Read', and 'Real Time Response
(Admin): Write'.
.PARAMETER QueueOffline
Add command request to the offline queue
.PARAMETER Include
Include additional properties
.PARAMETER Id
Host identifier
.LINK
https://github.com/crowdstrike/psfalcon/wiki/Uninstall-FalconSensor
#>

  [CmdletBinding(SupportsShouldProcess)]
  param(
    [Parameter(Position=1)]
    [boolean]$QueueOffline,
    [Parameter(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(Mandatory,ValueFromPipelineByPropertyName,ValueFromPipeline,Position=3)]
    [ValidatePattern('^[a-fA-F0-9]{32}$')]
    [Alias('HostId','device_id','host_ids','aid')]
    [string]$Id
  )
  process {
    try {
      [string[]]$Select = 'cid','device_id','platform_name','device_policies'
      if ($Include) { $Select += $Include }
      $HostList = Get-FalconHost -Id $Id | Select-Object $Select
      if ($HostList.platform_name -notmatch '^(Windows|Linux)$') {
        throw 'Only Windows and Linux hosts are currently supported for uninstallation using PSFalcon.'
      }
      [string]$Filename = if ($HostList.platform_name -eq 'Linux') {
        'uninstall_sensor.sh'
      } else {
        'uninstall_sensor.ps1'
      }
      [string]$Script = Get-Content (Join-Path (Join-Path (Show-FalconModule).ModulePath 'script') $Filename) -Raw
      $Param = @{
        Command = 'runscript'
        Argument = '-Raw=```{0}```' -f $Script
        Timeout = 120
        QueueOffline = if ($QueueOffline) { $QueueOffline } else { $false }
      }
      [string]$IdValue = switch ($HostList.device_policies.sensor_update.uninstall_protection) {
        'ENABLED' { $HostList.device_id }
        'MAINTENANCE_MODE' { 'MAINTENANCE' }
      }
      if ($IdValue) {
        [string]$Token = ($IdValue | Get-FalconUninstallToken -AuditMessage ("Uninstall-FalconSensor [$(
          (Show-FalconModule).UserAgent)]"
)).uninstall_token
        if ($Token) { $Param.Argument += (' -CommandLine=```{0}```' -f $Token) }
      }
      $Request = $HostList | Invoke-FalconRtr @Param
      if ($Request) {
        [string[]]$Select = 'cid','device_id'
        if ($Include) { $Select += $Include }
        @($HostList | Select-Object $Select).foreach{
          $Status = if ($Request.stdout) {
            ($Request.stdout).Trim()
          } elseif (!$Request.stdout -and $QueueOffline -eq $true) {
            'Uninstall request queued'
          } else {
            $Request.stderr
          }
          Set-Property $_ 'status' $Status
          $_
        }
      }
    } catch {
      throw $_
    }
  }
}