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/Importing,-Syntax-and-Output
#>

    [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) -and $ExportParam) {
            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/Third-party-ingestion
#>

    [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)]
        [System.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)"
                        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 X 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/Third-party-ingestion
#>

    [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 | Select-Object -Unique) -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.
#>

    [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 = 'crowdstrike-psfalcon',$ModuleData.ModuleVersion -join '/'
            }
        } else {
            throw "Unable to locate '$ManifestPath'."
        }
    }
}