psfalcon icon indicating copy to clipboard operation
psfalcon copied to clipboard

[ ENHANCEMENT ] Add error message when `Receive-FalconInstaller` fails due to timeout

Open slolife opened this issue 1 year ago • 11 comments

Describe the bug Receive-FalconInstaller does not create file or save download to file. No errors returned.

To Reproduce Run script below. All query requests run fine and return data.

#Requires -Version 5.1
using module @{ ModuleName = 'PSFalcon'; ModuleVersion = '2.2' }

# Ensure that we have a valid CrowdStrike API token
Invoke-Expression -Command .\GetToken.ps1 | Out-Null

$Installers = Get-FalconInstaller -Filter "platform:'windows'+os:'Windows'" -Detailed

if ($Installers) {
    # Select latest installer
    $Latest = $Installers | Sort-Object -Property release_date -Descending | Select -first 1 #Where-Object { $_.version -match "^\d\.\d{1,2}\.$BuildVersion" }
	Write-Output $Latest

	if ($Latest) {
		$InstallerId = $Latest.sha256
		$Filename = $Latest.name
		$FileVersion = $Latest.version
    }
} else {
     throw "Error retrieving installer list"
}

$OutputDir = "$pwd\Output"
New-Item -Path "$OutputDir" -Force -ItemType Directory | Out-Null

$VersionedFileName = $Filename.replace('.exe', '-' + "$FileVersion" + '.exe')
$OutputPath = "$OutputDir\$VersionedFileName"

Write-Output "Downloading installer"
Receive-FalconInstaller -Id $InstallerId -Force -Path "$OutputPath"

Expected behavior When Receive-FalconInstaller call completes, it either throws exception or file is on disk

Environment:

  • OS: Windows 10[e.g. Windows Server 2016, Windows 10]
  • PowerShell: 5.1.19041.2673
  • PSFalcon: v2.2.5 {d893eb9f-f6bb-4a40-9caf-aaff0e42acd1}

Additional context


Windows PowerShell transcript start
Start time: 20230510134942
Configuration Name: 
Machine: (Microsoft Windows NT 10.0.19045.0)
Host Application: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
Process ID: 28700
PSVersion: 5.1.19041.2673
PSEdition: Desktop
PSCompatibleVersions: 1.0, 2.0, 3.0, 4.0, 5.0, 5.1.19041.2673
BuildVersion: 10.0.19041.2673
CLRVersion: 4.0.30319.42000
WSManStackVersion: 3.0
PSRemotingProtocolVersion: 2.3
SerializationVersion: 1.1.0.1
**********************
Transcript started, output file is PowerShell_transcript.qH4m9R3F.20230510134942.txt
PS C:\dev\crowdstrike-psfalcon> .\DownloadLatestSensors.ps1
VERBOSE: 13:49:48 [ApiClient.Invoke] POST https://api.us-2.crowdstrike.com/oauth2/token
VERBOSE: 13:49:48 [ApiClient.Invoke] ContentType=application/x-www-form-urlencoded, Accept=application/json
VERBOSE: 13:49:48 [ApiClient.Invoke] 201: Created
VERBOSE: 13:49:48 [ApiClient.Invoke] Connection=keep-alive, X-Cs-Region=us-2, X-Cs-Traceid=337ae88d-a1ff-4fd2-975c-2f9274a6a016, X-Ratelimit-Limit=300, X-Ratelimit-Remaining=299, Strict-Transport-Security=max-age=31536000; includeSubDomains, Date=Wed, 10 May 2023 20:49:47 GMT, Server=nginx
VERBOSE: 13:49:48 [Request-FalconToken] Authorized until: 05/10/2023 14:19:47
VERBOSE: 13:49:48 [Get-FalconInstaller] /sensors/combined/installers/v1:get
VERBOSE: 13:49:48 [ApiClient.Invoke] GET https://api.us-2.crowdstrike.com/sensors/combined/installers/v1?filter=platform:'windows'%2Bos:'Windows'
VERBOSE: 13:49:48 [ApiClient.Invoke] Accept=application/json
VERBOSE: 13:49:49 [ApiClient.Invoke] 200: OK
VERBOSE: 13:49:49 [ApiClient.Invoke] Connection=keep-alive, Strict-Transport-Security=max-age=15724800; includeSubDomains, max-age=31536000; includeSubDomains, X-Cs-Region=us-2, X-Cs-Traceid=298467e6-c120-43e5-bba2-5fbb6f150587, X-Ratelimit-Limit=6000, X-Ratelimit-Remaining=5999, Date=Wed, 10 May 2023 20:49:47 GMT, Server=nginx
VERBOSE: 13:49:49 [Write-Result] query_time=0.097343066, powered_by=binserv, trace_id=298467e6-c120-43e5-bba2-5fbb6f150587


name         : WindowsSensor.MaverickGyr.exe
description  : Falcon Sensor for Windows
platform     : windows
os           : Windows
os_version   :
sha256       : a5059da06d318e1766a2ce95d45d1ac1897443f62e2f2326a1d17c7347d1e7a1
release_date : 2023-05-02T22:07:45.558Z
version      : 6.54.16808
file_size    : 159103520
file_type    : exe

Downloading installer
VERBOSE: 13:49:49 [Receive-FalconInstaller] /sensors/entities/download-installer/v1:get
VERBOSE: 13:49:49 [ApiClient.Invoke] GET https://api.us-2.crowdstrike.com/sensors/entities/download-installer/v1?id=a5059da06d318e1766a2ce95d45d1ac1897443f62e2f2326a1d17c7347d1e7a1
VERBOSE: 13:49:49 [ApiClient.Invoke] Accept=application/octet-stream

slolife avatar May 10 '23 21:05 slolife

I suspect it's how you're building the output path that's causing a problem, but when I tried running your script it worked fine.

I tested Receive-FalconInstaller a couple of different ways to verify that the command itself works. It downloads properly when using the pipeline with an installer result:

PS C:\falcon> Get-FalconInstaller -Detailed -Limit 1 -Filter "platform:'windows'" | Receive-FalconInstaller
VERBOSE: 12:08:23 [Get-FalconInstaller] /sensors/combined/installers/v1:get
VERBOSE: 12:08:23 [ApiClient.Invoke] GET
https://api.crowdstrike.com/sensors/combined/installers/v1?limit=1&filter=platform:'windows'
VERBOSE: 12:08:23 [ApiClient.Invoke] Accept=application/json
VERBOSE: 12:08:23 [ApiClient.Invoke] 200: OK
VERBOSE: 12:08:23 [ApiClient.Invoke] Connection=keep-alive, Strict-Transport-Security=max-age=15724800;
includeSubDomains, max-age=31536000; includeSubDomains, X-Cs-Region=us-1,
X-Cs-Traceid=42bf7ba5-c907-49e9-8a08-2f28bb8af28c, X-Ratelimit-Limit=6000, X-Ratelimit-Remaining=5991, Date=Thu, 11 May
 2023 19:08:24 GMT, Server=nginx
VERBOSE: 12:08:23 [Write-Result] query_time=0.099462462, powered_by=binserv,
trace_id=42bf7ba5-c907-49e9-8a08-2f28bb8af28c
VERBOSE: 12:08:23 [Receive-FalconInstaller] /sensors/entities/download-installer/v1:get
VERBOSE: 12:08:23 [ApiClient.Invoke] GET
https://api.crowdstrike.com/sensors/entities/download-installer/v1?id=47052db19bef20879fc470e2b76e336420d5c23bc6e1ce2b9
57cc546b024e185
VERBOSE: 12:08:23 [ApiClient.Invoke] Accept=application/octet-stream
VERBOSE: 12:08:31 [ApiClient.Invoke] Output directed to 'C:\falcon\WindowsSensor.exe'.

FullName                                 Length LastWriteTime
--------                                 ------ -------------
C:\falcon\WindowsSensor.exe 159103096 5/11/2023 12:08:31 PM

And when using values saved in a variable:

PS C:\falcon> $Installer = Get-FalconInstaller -Detailed -Limit 1 -Filter "platform:'windows'"
VERBOSE: 12:09:30 [Get-FalconInstaller] /sensors/combined/installers/v1:get
VERBOSE: 12:09:30 [ApiClient.Invoke] GET
https://api.crowdstrike.com/sensors/combined/installers/v1?limit=1&filter=platform:'windows'
VERBOSE: 12:09:30 [ApiClient.Invoke] Accept=application/json
VERBOSE: 12:09:30 [ApiClient.Invoke] 200: OK
VERBOSE: 12:09:30 [ApiClient.Invoke] Connection=keep-alive, Strict-Transport-Security=max-age=15724800;
includeSubDomains, max-age=31536000; includeSubDomains, X-Cs-Region=us-1,
X-Cs-Traceid=87554e8f-d97f-4818-b03d-cafc859b9a59, X-Ratelimit-Limit=6000, X-Ratelimit-Remaining=5992, Date=Thu, 11 May
 2023 19:09:31 GMT, Server=nginx
VERBOSE: 12:09:30 [Write-Result] query_time=0.027503389, powered_by=binserv,
trace_id=87554e8f-d97f-4818-b03d-cafc859b9a59
PS C:\falcon> Receive-FalconInstaller -Id $Installer.sha256 -Force -Path C:\falcon\WindowsSensor.exe
VERBOSE: 12:10:04 [Receive-FalconInstaller] /sensors/entities/download-installer/v1:get
VERBOSE: 12:10:04 [ApiClient.Invoke] GET
https://api.crowdstrike.com/sensors/entities/download-installer/v1?id=47052db19bef20879fc470e2b76e336420d5c23bc6e1ce2b9
57cc546b024e185
VERBOSE: 12:10:04 [ApiClient.Invoke] Accept=application/octet-stream
VERBOSE: 12:10:13 [ApiClient.Invoke] Output directed to 'C:\falcon\WindowsSensor.exe'.

FullName                       Length LastWriteTime
--------                       ------ -------------
C:\falcon\WindowsSensor.exe 159103096 5/11/2023 12:10:13 PM

Could you try updating your path creation method? Here's how I would do it:

$OutputDir = Join-Path (Get-Location).Path Output
$Installer = Get-FalconInstaller -Detailed -Limit 1 -Filter "platform:'windows'+os:'Windows'" -Sort release_date.desc
if (!$Installer) { throw "No installer result." }
if ((Test-Path $OutputDir) -eq $false) { [void](New-Item -Path $OutputDir -ItemType Directory) }
$Filename = (($Installer.name -replace '\.exe$'),$Installer.version -join '-'),'exe' -join '.'
Receive-FalconInstaller -Id $Installer.sha256 -Path (Join-Path $OutputDir $Filename) -Force

bk-cs avatar May 11 '23 19:05 bk-cs

I tried the code you posted:

#Requires -Version 5.1
using module @{ ModuleName = 'PSFalcon'; ModuleVersion = '2.2' }

$OutputDir = Join-Path (Get-Location).Path Output
$Installer = Get-FalconInstaller -Detailed -Limit 1 -Filter "platform:'windows'+os:'Windows'" -Sort release_date.desc
if (!$Installer) { throw "No installer result." }
if ((Test-Path $OutputDir) -eq $false) { [void](New-Item -Path $OutputDir -ItemType Directory) }
$Filename = (($Installer.name -replace '\.exe$'),$Installer.version -join '-'),'exe' -join '.'
Receive-FalconInstaller -Id $Installer.sha256 -Path (Join-Path $OutputDir $Filename) -Force

And that did not write the file.

I also tried what you posted: Get-FalconInstaller -Detailed -Limit 1 -Filter "platform:'windows'" | Receive-FalconInstaller

Either case, I am not getting the following message at the end: [ApiClient.Invoke] Output directed to 'C:\falcon\WindowsSensor.exe'

Any other thoughts as to what might be different about my setup/machine?

slolife avatar May 11 '23 23:05 slolife

Could you try removing and reinstalling the module?

Uninstall-Module -Name PSFalcon -AllVersions
Install-Module -Name PSFalcon -Scope CurrentUser

bk-cs avatar May 12 '23 15:05 bk-cs

Strange. I run

Uninstall-Module -Name PSFalcon -AllVersions
Install-Module -Name PSFalcon -Scope CurrentUser

And then run Get-FalconInstaller -Detailed -Limit 1 -Filter "platform:'windows'" | Receive-FalconInstaller And it works (correctly downloads and saves the file to disk).

Run the Get/Receive line again and it doesn't save the file.

Uninstall/Re-install again and it still doesn't work. I have to uninstall, reboot, then install and get 1 run that works.

slolife avatar May 12 '23 20:05 slolife

It is seems that this might be a timeout issue during download on 1 machine. I ran it on two other machines with faster connections and both worked repeatedly.

I changed call to download macOS and Ubuntu images on the problem machine and those worked repeatedly, so definitely seems like a timeout issue.

Any ideas on lack of error message for a timeout?

slolife avatar May 13 '23 00:05 slolife

Thanks for following up! I'll do some research and determine if I can add an error message when the download fails due to timeout.

bk-cs avatar May 15 '23 15:05 bk-cs

I experimented with this to see if it was possible, but I've yet to figure out a way to output an error given how PSFalcon is downloading the file. Keeping this open in case I figure it out...

bk-cs avatar Nov 22 '23 19:11 bk-cs

Greeting, I was also having this issue..

I worked out a work around by adding a timeout value to the class.ps1 as below.

So I am now able to download all my missing windows clients.

  ApiClient() {
    $this.Handler = [System.Net.Http.HttpClientHandler]::New()
    $this.Client = [System.Net.Http.HttpClient]::New($this.Handler)
    # added a large timeout...
    $this.Client.Timeout = New-Object System.TimeSpan(0, 10, 90);

    $this.Collector = $null
  }

I had tried to add it to the download section .. as follows..

      $Request = if ($Param.Outfile) {
        # Download file
        @($Param.Headers.Keys).foreach{ $this.Client.DefaultRequestHeaders.Add($_,$Param.Headers.$_) }
        #  Added timeout
        $this.Client.Timeout = New-Object System.TimeSpan(0, 10, 90);
        $this.Verbose('ApiClient.Invoke','Receiving ByteArray content...')
        $this.Client.GetByteArrayAsync($Param.Path)
      }

As the $this.Client object it is reused, if you use multiple calls you get the following reponse..

Exception setting "Timeout": "This instance has already started one or more requests. Properties can only be modified before sending the first request."
At line:1 char:1
+ $this.Client.Timeout = New-Object System.TimeSpan(0, 10, 90);
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], SetValueInvocationException
    + FullyQualifiedErrorId : ExceptionWhenSetting

MatthewCKelly avatar Dec 18 '23 03:12 MatthewCKelly

Thank you @MatthewCKelly . I will try this out. Right now, all of my downloads are working without adding this timeout, but I am sure I will reproduce again sometime soon with a slow connection.

CBecker-EDR avatar Dec 20 '23 01:12 CBecker-EDR

I wasn't able to get that timeout option working without the "re-use" errors that @MatthewCKelly reported. I'm still experimenting...

bk-cs avatar Dec 20 '23 01:12 bk-cs

I wasn't able to get that timeout option working without the "re-use" errors that @MatthewCKelly reported. I'm still experimenting...

I have a script that does the following..

  1. Gathers all the current installers.
  2. Then all the SensorUpdate policies,
  3. Enumerates all the Policies for membership and then add to an array all the policies that have more than than 10 members and have a sensor assigned and windows
  4. create a folder for each version that doesnt exist
  5. downloads each file that doesnt exist.
  6. gets the hash of each
  7. then emails the security team once complete any updates. as I am doing multiple calls, the class is loaded only the once therefore as the timeout is set you cannot re-set it.. Hence I moved it to earlier in the class..

MatthewCKelly avatar Dec 20 '23 02:12 MatthewCKelly