public/psf-output.ps1
function Export-FalconReport { <# .SYNOPSIS Format a response object and output to console or CSV .DESCRIPTION Each property within a response object is 'flattened' to a single field containing a CSV-compatible value--with each column having an appended 'prefix'--and then exported to the console or designated file path. For instance, if the object contains a property called 'device_policies', and that contains other objects called 'prevention' and 'sensor_update', the result will contain properties labelled 'device_policies.prevention' and 'device_policies.sensor_update' with additional '.<field_name>' values for any sub-properties of those objects. When the result contains an array with similarly named properties, it will attempt to add each sub-property with an additional 'id' prefix based on the value of an existing 'id' or 'policy_id' property. For example, @{ hosts = @( @{ device_id = 123; hostname = 'abc' }, @{ device_id = 456; hostname = 'def' })} will be displayed under the columns 'hosts.123.hostname' and 'hosts.456.hostname'. The 'device_id' property is excluded as it becomes a column. There is potential for data loss due to object manipulation. Use 'ConvertTo-Json' to ensure all object properties are retained when integrity is a concern. .PARAMETER Path Destination path .PARAMETER Object Response object to format .PARAMETER Force Overwrite an existing file when present .LINK https://github.com/crowdstrike/psfalcon/wiki/Export-FalconReport #> [CmdletBinding()] param( [Parameter(Position=1)] [ValidatePattern('\.csv$')] [string]$Path, [Parameter(Mandatory,ValueFromPipeline,Position=2)] [object[]]$Object ) begin { function Get-Array ($Array,$Output,$Name) { foreach ($Item in $Array) { if ($Item.PSObject.TypeNames -contains 'System.Management.Automation.PSCustomObject') { # Add sub-objects to output $IdField = $Item.PSObject.Properties.Name -match '^(id|(device|policy)_id)$' if ($IdField) { $ObjectParam = @{ Object = $Item | Select-Object -ExcludeProperty $IdField Output = $Output Prefix = $Name,$Item.$IdField -join '.' } Get-PSObject @ObjectParam } else { $ObjectParam = @{ Object = $Item; Output = $Output; Prefix = $Name } Get-PSObject @ObjectParam } } else { # Add property to output as 'name' $SetParam = @{ Object = $Output; Name = $Name; Value = $Array -join ',' } Set-Property @SetParam } } } function Get-PSObject ($Object,$Output,$Prefix) { foreach ($Item in ($Object.PSObject.Properties | Where-Object { $_.MemberType -eq 'NoteProperty' })) { if ($Item.Value.PSObject.TypeNames -contains 'System.Object[]') { # Add array members to output with 'prefix.name' $ArrayParam = @{ Array = $Item.Value Output = $Output Name = if ($Prefix) { $Prefix,$Item.Name -join '.' } else { $Item.Name } } Get-Array @ArrayParam } elseif ($Item.Value.PSObject.TypeNames -contains 'System.Management.Automation.PSCustomObject') { # Add sub-objects to output with 'prefix.name' $ObjectParam = @{ Object = $Item.Value Output = $Output Prefix = if ($Prefix) { $Prefix,$Item.Name -join '.' } else { $Item.Name } } Get-PSObject @ObjectParam } else { # Add property to output with 'prefix.name' $SetParam = @{ Object = $Output Name = if ($Prefix) { $Prefix,$Item.Name -join '.' } else { $Item.Name } Value = $Item.Value } Set-Property @SetParam } } } if ($Path) { $Path = $Script:Falcon.Api.Path($Path) } $List = [System.Collections.Generic.List[object]]@() } process { if ($Object) { @($Object).foreach{ $List.Add($_) }}} end { $OutPath = Test-OutFile $Path if ($OutPath.Category -eq 'WriteError' -and !$Force) { Write-Error @OutPath } elseif ($List) { [object[]]$Output = @($List).foreach{ $i = [PSCustomObject]@{} if ($_.PSObject.TypeNames -contains 'System.Management.Automation.PSCustomObject') { # Add sorted properties to output Get-PSObject $_ $i } else { # Add strings to output as 'id' Set-Property $i id $_ } $i } if ($Output) { # Select all available property names [string[]]$Select = @($Output).foreach{ $_.PSObject.Properties.Name } | Select-Object -Unique if ($Path) { # Export to CSV, forcing all properties $Output | Select-Object $Select | Export-Csv $Path -NoTypeInformation -Append } else { # Export to console, forcing all properties $Output | Select-Object $Select } } } if ($Path -and (Test-Path $Path)) { Get-ChildItem $Path | Select-Object FullName,Length,LastWriteTime } } } function Send-FalconWebhook { <# .SYNOPSIS Send a PSFalcon object to a supported Webhook .DESCRIPTION Sends an object to a Webhook, converting the object to an acceptable format when required .PARAMETER Type Webhook type .PARAMETER Path Webhook URL .PARAMETER Label Message label .PARAMETER Object Response object to format .LINK https://github.com/crowdstrike/psfalcon/wiki/Send-FalconWebhook #> [CmdletBinding()] param( [Parameter(Mandatory,Position=1)] [ValidateSet('Slack')] [string]$Type, [Parameter(Mandatory,Position=2)] [System.Uri]$Path, [Parameter(Position=3)] [string]$Label, [Parameter(Mandatory,ValueFromPipeline,Position=4)] [object]$Object ) begin { $Token = if ($Script:Falcon.Api.Client.DefaultRequestHeaders.Authorization) { # Remove default Falcon authorization token $Script:Falcon.Api.Client.DefaultRequestHeaders.Authorization [void]$Script:Falcon.Api.Client.DefaultRequestHeaders.Remove('Authorization') } } process { [object[]]$Content = switch ($PSBoundParameters.Type) { 'Slack' { # Create 'attachment' for each object in submission @($Object | Export-FalconReport).foreach{ [object[]]$Fields = @($_.PSObject.Properties).foreach{ ,@{ title = $_.Name value = if ($_.Value -is [boolean]) { # Convert [boolean] to [string] if ($_.Value -eq $true) { 'true' } else { 'false' } } else { # Add [string] value when $null if ($null -eq $_.Value) { 'null' } else { $_.Value } } short = $false } } ,@{ username = 'PSFalcon',$Script:Falcon.ClientId -join ': ' icon_url = 'https://raw.githubusercontent.com/CrowdStrike/psfalcon/master/icon.png' text = $PSBoundParameters.Label attachments = @( @{ fallback = 'Send-FalconWebhook' fields = $Fields } ) } } } } foreach ($Item in $Content) { try { $Param = @{ Path = $PSBoundParameters.Path Method = 'post' Headers = @{ ContentType = 'application/json' } Body = ConvertTo-Json $Item -Depth 32 } $Request = $Script:Falcon.Api.Invoke($Param) Write-Result $Request } catch { throw $_ } } } end { if ($Token -and !$Script:Falcon.Api.Client.DefaultRequestHeaders.Authorization) { # Restore default Falcon authorization token $Script:Falcon.Api.Client.DefaultRequestHeaders.Add('Authorization',$Token) } } } function Show-FalconMap { <# .SYNOPSIS Display indicators on the Falcon Intelligence Indicator Map .DESCRIPTION Your default web browser will be used to view the Indicator Map. Show-FalconMap will accept domains, SHA256 hashes, IP addresses and URLs. Invalid indicator values are ignored. .PARAMETER Indicator Indicator to display on the Indicator map .LINK https://github.com/crowdstrike/psfalcon/wiki/Show-FalconMap #> [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory,ValueFromPipeline,Position=1)] [Alias('Indicators')] [string[]]$Indicator ) begin { [string]$FalconUI = "$($Script:Falcon.Hostname -replace 'api','falcon')" $List = [System.Collections.Generic.List[string]]@() } process { if ($Indicator) { @($Indicator).foreach{ if ($_ -match '^(domain|hash|ip_address|url)_') { # Split indicators from 'Get-FalconIndicator' [string]$String = switch -Regex ($_) { '^domain_' { $_ -replace '_',':' } '^hash_sha256' { @('hash',($_ -split '_')[-1]) -join ':' } '^ip_address_' { $_ -replace '_address_',':' } '^url_' { $Value = ([System.Uri]($_ -replace 'url_',$null)).Host $Type = Test-RegexValue $Value if ($Type -match 'ipv(4|6)') { $Type = 'ip' } if ($Type -and $Value) { @($Type,$Value) -join ':' } } } if ($String) { $List.Add($String) } } else { $Type = Test-RegexValue $_ if ($Type -match 'ipv(4|6)') { $Type = 'ip' } $Value = if ($Type -match '^(domain|md5|sha256)$') { $_.ToLower() } else { $_ } if ($Type -and $Value) { $List.Add("$($Type):'$Value'") } } } } } end { if ($List) { [string[]]$IocInput = @($List) -join ',' if (!$IocInput) { throw "No valid indicators found." } [string]$Target = "$($FalconUI)/intelligence/graph?indicators=$($IocInput -join ',')" if ($PSCmdlet.ShouldProcess($Target)) { Start-Process $Target } } } } function Show-FalconModule { <# .SYNOPSIS Display information about your PSFalcon module .DESCRIPTION Outputs an object containing module, user and system version information that is helpful for diagnosing problems with the PSFalcon module. .LINK https://github.com/crowdstrike/psfalcon/wiki/Show-FalconModule #> [CmdletBinding()] param() process { $ManifestPath = Join-Path (Split-Path $PSScriptRoot -Parent) 'PSFalcon.psd1' if (Test-Path $ManifestPath) { $ModuleData = Import-PowerShellDataFile -Path $ManifestPath [PSCustomObject]@{ PSVersion = "$($PSVersionTable.PSEdition) [$($PSVersionTable.PSVersion)]" ModuleVersion = "v$($ModuleData.ModuleVersion) {$($ModuleData.GUID)}" ModulePath = Split-Path $ManifestPath -Parent UserModulePath = $env:PSModulePath UserHome = $HOME UserAgent = $Script:Falcon.Api.UserAgent } } else { throw "Unable to locate '$ManifestPath'." } } } |