Skip to content

Commit aec9f45

Browse files
authored
Merge pull request #236 from CrowdStrike/2.2.2
2.2.2
2 parents 8e28dcb + e79eceb commit aec9f45

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+5328
-5199
lines changed

PSFalcon.psd1

Lines changed: 241 additions & 291 deletions
Large diffs are not rendered by default.

Private/Private.ps1

Lines changed: 114 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -536,11 +536,79 @@ function Invoke-Falcon {
536536
[string]$HostUrl
537537
)
538538
begin {
539+
function Invoke-Loop ([hashtable]$Splat,[object]$Object,[int]$Int) {
540+
do {
541+
# Determine next offset value
542+
[string[]]$Next = if ($Object.after) {
543+
@('after',$Object.after)
544+
} elseif ($Object.next_token) {
545+
@('next_token',$Object.next_token)
546+
} elseif ($null -ne $Object.offset) {
547+
$Value = if ($Object.offset -match '^\d{1,}$') { $Int } else { $Object.offset }
548+
@('offset',$Value)
549+
}
550+
if ($Next) {
551+
# Clone parameters and make request
552+
$Clone = $Splat.Clone()
553+
$Clone.Endpoint = $Splat.Endpoint.Clone()
554+
$Clone.Endpoint.Path = if ($Clone.Endpoint.Path -match "$($Next[0])=\d{1,}") {
555+
# If offset was input, continue from that value
556+
$Current = [regex]::Match($Clone.Endpoint.Path,'offset=(\d+)(^&)?').Captures.Value
557+
$Next[1] += [int]$Current.Split('=')[-1]
558+
$Clone.Endpoint.Path -replace $Current,($Next -join '=')
559+
} elseif ($Clone.Endpoint.Path -match "$($Splat.Endpoint)^" -and
560+
$Clone.Endpoint.Path -notmatch '\?') {
561+
# Add pagination
562+
$Clone.Endpoint.Path,($Next -join '=') -join '?'
563+
} else {
564+
# Append pagination
565+
$Clone.Endpoint.Path,($Next -join '=') -join '&'
566+
}
567+
if ($Script:Falcon.Expiration -le (Get-Date).AddSeconds(60)) { Request-FalconToken }
568+
$Script:Falcon.Api.Invoke($Clone.Endpoint) | ForEach-Object {
569+
if ($_.Result.Content) {
570+
# Output result, update pagination and received count
571+
$Object = (ConvertFrom-Json (
572+
$_.Result.Content).ReadAsStringAsync().Result).meta.pagination
573+
Write-Request $Clone $_ -OutVariable Output
574+
[int]$Int += ($Output | Measure-Object).Count
575+
if ($Object.total) {
576+
Write-Verbose "[Invoke-Falcon] Retrieved $Int of $($Object.total)"
577+
}
578+
} elseif ($Object.total) {
579+
[string]$Message = "[Invoke-Falcon] Total results limited by API '$(
580+
($Clone.Endpoint.Path).Split('?')[0] -replace $Script:Falcon.Hostname,
581+
$null)' ($Int of $($Object.total))."
582+
Write-Error $Message
583+
}
584+
}
585+
}
586+
} while ( $Object.total -and $Int -lt $Object.total )
587+
}
588+
function Write-Request {
589+
[CmdletBinding()]
590+
param(
591+
[hashtable]$Splat,
592+
[object]$Object
593+
)
594+
[boolean]$NoDetail = if ($Splat.Endpoint.Path -match '(/combined/|/rule-groups-full/)') {
595+
# Determine if endpoint requires a secondary 'Detailed' request
596+
$true
597+
} else {
598+
$false
599+
}
600+
if ($Splat.Detailed -eq $true -and $NoDetail -eq $false) {
601+
$Output = Write-Result $Object
602+
if ($Output) { & $Command -Id $Output }
603+
} else {
604+
Write-Result $Object
605+
}
606+
}
539607
if (!$Script:Falcon.Api.Client.DefaultRequestHeaders.Authorization -or !$Script:Falcon.Hostname) {
540-
# Request initial authorization token
608+
# Force initial authorization token request
541609
Request-FalconToken
542610
}
543-
# Gather parameters for 'Get-ParamSet'
611+
# Gather request parameters and split into groups
544612
$GetParam = @{}
545613
$PSBoundParameters.GetEnumerator().Where({ $_.Key -notmatch '^(Command|RawOutput)$' }).foreach{
546614
$GetParam.Add($_.Key,$_.Value)
@@ -581,120 +649,47 @@ function Invoke-Falcon {
581649
$_.Name -eq $Endpoint }).Parameters.Where({ $_.Name -eq 'Limit' }).Attributes.MaxRange
582650
if ($Limit) { $Inputs.Add('Limit',$Limit) }
583651
}
584-
# Regex for URL paths that don't need a secondary 'Detailed' request
585-
[regex]$NoDetail = '(/combined/|/rule-groups-full/)'
586652
}
587653
process {
588-
foreach ($Set in (Get-ParamSet @GetParam)) {
589-
[string]$Operation = $Set.Endpoint.Method.ToUpper()
590-
[string]$Target = New-ShouldMessage $Set.Endpoint
591-
try {
592-
# Refresh authorization token during loop
654+
Get-ParamSet @GetParam | ForEach-Object {
655+
[string]$Operation = $_.Endpoint.Method.ToUpper()
656+
[string]$Target = New-ShouldMessage $_.Endpoint
657+
if ($_.Endpoint.Headers.ContentType -eq 'application/json' -and $_.Endpoint.Body) {
658+
# Convert body to Json
659+
$_.Endpoint.Body = ConvertTo-Json $_.Endpoint.Body -Depth 32 -Compress
660+
}
661+
if ($PSCmdlet.ShouldProcess($Target,$Operation)) {
593662
if ($Script:Falcon.Expiration -le (Get-Date).AddSeconds(60)) { Request-FalconToken }
594-
if ($Set.Endpoint.Headers.ContentType -eq 'application/json' -and $Set.Endpoint.Body) {
595-
# Convert body to Json
596-
$Set.Endpoint.Body = ConvertTo-Json $Set.Endpoint.Body -Depth 32 -Compress
597-
}
598-
$Request = if ($PSCmdlet.ShouldProcess($Target,$Operation)) {
599-
$Script:Falcon.Api.Invoke($Set.Endpoint)
600-
}
601-
if ($RawOutput) {
602-
# Return result if 'RawOutput' is defined
603-
$Request
604-
} elseif ($Set.Endpoint.Outfile -and (Test-Path $Set.Endpoint.Outfile)) {
605-
# Display 'Outfile'
606-
Get-ChildItem $Set.Endpoint.Outfile | Select-Object FullName,Length,LastWriteTime
607-
} elseif ($Request.Result.Content) {
608-
# Capture pagination for 'Total' and 'All'
609-
$Pagination = (ConvertFrom-Json (
610-
$Request.Result.Content).ReadAsStringAsync().Result).meta.pagination
611-
if ($Set.Total -eq $true -and $Pagination) {
612-
# Output 'Total'
613-
$Pagination.total
614-
} else {
615-
$Result = Write-Result $Request
616-
if ($null -ne $Result) {
617-
if ($Set.Detailed -eq $true -and $Set.Endpoint.Path -notmatch $NoDetail) {
618-
# Output 'Detailed'
619-
& $Command -Id $Result
620-
} else {
621-
# Output result
622-
$Result
623-
}
624-
if ($Set.All -eq $true -and ($Result | Measure-Object).Count -lt
625-
$Pagination.total) {
663+
try {
664+
$Request = $Script:Falcon.Api.Invoke($_.Endpoint)
665+
if ($_.Endpoint.Outfile -and (Test-Path $_.Endpoint.Outfile)) {
666+
# Display 'Outfile'
667+
Get-ChildItem $_.Endpoint.Outfile | Select-Object FullName,Length,LastWriteTime
668+
} elseif ($Request -and $RawOutput) {
669+
# Return result if 'RawOutput' is defined
670+
$Request
671+
} elseif ($Request.Result.Content) {
672+
# Capture pagination for 'Total' and 'All'
673+
$Pagination = (ConvertFrom-Json (
674+
$Request.Result.Content).ReadAsStringAsync().Result).meta.pagination
675+
if ($Pagination.total -and $_.Total -eq $true) {
676+
# Output 'Total'
677+
$Pagination.total
678+
} else {
679+
Write-Request $_ $Request -OutVariable Result
680+
if ($Result -and $_.All -eq $true) {
626681
# Repeat request(s)
627-
Invoke-Loop $Set $Pagination $Result
682+
[int]$Count = ($Result | Measure-Object).Count
683+
if ($Pagination.total -and $Count -lt $Pagination.total) {
684+
Write-Verbose "[Invoke-Falcon] Retrieved $Count of $($Pagination.total)"
685+
Invoke-Loop $_ $Pagination $Count
686+
}
628687
}
629688
}
630689
}
690+
} catch {
691+
Write-Error $_
631692
}
632-
} catch {
633-
Write-Error $_
634-
}
635-
}
636-
}
637-
}
638-
function Invoke-Loop {
639-
[CmdletBinding()]
640-
param(
641-
[Parameter(Mandatory)]
642-
[System.Collections.Hashtable]$ParamSet,
643-
[Parameter(Mandatory)]
644-
[System.Object]$Pagination,
645-
[Parameter(Mandatory)]
646-
[System.Object]$Result
647-
)
648-
begin {
649-
# Regex for URL paths that don't need a secondary 'Detailed' request
650-
[regex]$NoDetail = '(/combined/|/rule-groups-full/)'
651-
}
652-
process {
653-
for ($i = ($Result | Measure-Object).Count; $Pagination.next_page -or $i -lt $Pagination.total;
654-
$i += ($Result | Measure-Object).Count) {
655-
Write-Verbose "[Invoke-Loop] $i of $($Pagination.total)"
656-
# Clone endpoint parameters and update pagination
657-
$Clone = $ParamSet.Clone()
658-
$Clone.Endpoint = $ParamSet.Endpoint.Clone()
659-
$Page = if ($Pagination.after) {
660-
@('after',$Pagination.after)
661-
} elseif ($Pagination.next_token) {
662-
@('next_token',$Pagination.next_token)
663-
} elseif ($Pagination.next_page) {
664-
@('offset',$Pagination.offset)
665-
} elseif ($Pagination.offset -match '^\d{1,}$') {
666-
@('offset',$i)
667-
} else {
668-
@('offset',$Pagination.offset)
669-
}
670-
$Clone.Endpoint.Path = if ($Clone.Endpoint.Path -match "$($Page[0])=\d{1,}") {
671-
# If offset was input, continue from that value
672-
$Current = [regex]::Match($Clone.Endpoint.Path,'offset=(\d+)(^&)?').Captures.Value
673-
$Page[1] += [int]$Current.Split('=')[-1]
674-
$Clone.Endpoint.Path -replace $Current,($Page -join '=')
675-
} elseif ($Clone.Endpoint.Path -match "$Endpoint^" -and $Clone.Endpoint.Path -notmatch '\?') {
676-
# Add pagination
677-
$Clone.Endpoint.Path,($Page -join '=') -join '?'
678-
} else {
679-
# Update pagination
680-
$Clone.Endpoint.Path,($Page -join '=') -join '&'
681-
}
682-
$Request = $Script:Falcon.Api.Invoke($Clone.Endpoint)
683-
if ($Request.Result.Content) {
684-
$Result = Write-Result $Request
685-
if ($null -ne $Result) {
686-
if ($Clone.Detailed -eq $true -and $Clone.Endpoint.Path -notmatch $NoDetail) {
687-
& $Command -Id $Result
688-
} else {
689-
$Result
690-
}
691-
} else {
692-
[string]$Message = "[Invoke-Loop] Results limited by API '$(($Clone.Endpoint.Path).Split(
693-
'?')[0] -replace $Script:Falcon.Hostname,$null)' ($i of $($Pagination.total))."
694-
Write-Error $Message
695-
}
696-
$Pagination = (ConvertFrom-Json (
697-
$Request.Result.Content).ReadAsStringAsync().Result).meta.pagination
698693
}
699694
}
700695
}
@@ -714,31 +709,25 @@ function New-ShouldMessage {
714709
$Path = $Path -replace $Script:Falcon.Hostname,$null
715710
}
716711
if ($Path -match '\?') {
717-
# Add 'Path' without query values
712+
# Add 'Path' without query values, and 'Query' as an array
718713
[string[]]$Array = $Path -split '\?'
719714
[string[]]$Query = $Array[-1] -split '&'
720715
Set-Property $Output Path $Array[0]
716+
if ($Query) { Set-Property $Output Query $Query }
721717
} else {
722718
Set-Property $Output Path $Path
723719
}
724720
}
725721
if ($Object.Headers) {
726722
# Add 'Headers' value
727-
Set-Property $Output Headers ($Object.Headers.GetEnumerator().foreach{
723+
[string]$Header = ($Object.Headers.GetEnumerator().foreach{
728724
$_.Key,$_.Value -join '=' } -join ', ')
725+
if ($Header) { Set-Property $Output Headers $Header }
729726
}
730-
if ($Query) {
731-
# Add 'Query' value as an array
732-
Set-Property $Output Query $Query
733-
}
734-
foreach ($Pair in $Object.GetEnumerator().Where({ $_.Key -ne '^(Headers|Method|Path)$' })) {
735-
[string]$Value = switch ($Pair.Key) {
736-
'Body' {
737-
# Convert 'Body' to Json
738-
$Pair.Value | ConvertTo-Json -Depth 8
739-
}
740-
}
741-
if ($Value) { Set-Property $Output $Pair.Key $Value }
727+
if ($Object.Body -and $Object.Headers.ContentType -eq 'application/json') {
728+
# Add 'Body' value
729+
[string]$Body = try { $Object.Body | ConvertTo-Json -Depth 8 } catch {}
730+
if ($Body) { Set-Property $Output Body $Body }
742731
}
743732
"`r`n",($Output | Format-List | Out-String).Trim(),"`r`n" -join "`r`n"
744733
} catch {}
@@ -810,7 +799,8 @@ function Test-RegexValue {
810799
$RegEx = @{
811800
md5 = [regex]'^[A-Fa-f0-9]{32}$'
812801
sha256 = [regex]'^[A-Fa-f0-9]{64}$'
813-
ipv4 = [regex]'^([1-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.'
802+
ipv4 = [regex]('((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1' +
803+
'}[0-9])')
814804
ipv6 = [regex]'^[0-9a-fA-F]{1,4}:'
815805
domain = [regex]'^(https?://)?((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}$'
816806
email = [regex]"^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$"
@@ -832,7 +822,7 @@ function Test-RegexValue {
832822
}
833823
end {
834824
if ($Output) {
835-
Write-Verbose "[Test-RegexValue] $(@($Output,$String) -join ': ')"
825+
Write-Verbose "[Test-RegexValue] $(@((($Output | Out-String).Trim()),$String) -join ': ')"
836826
$Output
837827
}
838828
}

0 commit comments

Comments
 (0)