Created
December 22, 2020 07:44
-
-
Save gfraiteur/ab8d59bd19e60e5ac49c4730775ad3ea to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Param | |
( | |
[string] $artifactsPath, | |
[string] $scanResponsePath = '' | |
) | |
$ErrorActionPreference = "Stop" | |
trap | |
{ | |
Write-Output $_ | |
[Console]::Error.WriteLine("Script failed.") | |
Write-Output "##teamcity[buildStatus status='FAILURE']" | |
exit 1 | |
} | |
$apiKey = '***' | |
$serviceUrl = 'https://www.virustotal.com/vtapi/v2/' | |
$requestTimeout = 10*60 | |
$skipFiles = @('*src*', '*LicenseServer*', 'Index.xml') | |
# Located in C:\TeamCityData\, served as virtual directory by IIS as local user TeamCityDataServer. | |
# When changing the file, save a backup copy to \\diskstation\backup\TeamCityDataServer. | |
$configFileUrl = 'https://tc.postsharp.net/data/virustotal_config.txt' | |
<# | |
Config file example (json): | |
{ | |
"ignoredThreats": { | |
"Avira": ["ADWARE/Adware.Gen7", "ABC", "XYZ"] | |
} | |
} | |
#> | |
# This function requires .NET 4.5. | |
Function ZipFiles( $zipfilename, $sourcedir ) | |
{ | |
Add-Type -Assembly System.IO.Compression.FileSystem | |
$compressionLevel = [IO.Compression.CompressionLevel]::Optimal | |
[IO.Compression.ZipFile]::CreateFromDirectory($sourcedir, $zipfilename, $compressionLevel, $false) | |
} | |
Function WriteAsciiString( [IO.Stream] $stream, [String] $str ) | |
{ | |
$bytes = [Text.Encoding]::ASCII.GetBytes($str) | |
$stream.Write($bytes, 0, $bytes.Length) | |
} | |
[byte[]]$crlf = 13, 10 | |
Function WriteCRLF( $stream ) | |
{ | |
$stream.Write($crlf, 0, $crlf.Length) | |
} | |
# Prepare a zip package to send. | |
$packagePath = $artifactsPath + '.zip' | |
ZipFiles $packagePath $artifactsPath | |
# Send the package for scanning. | |
Try | |
{ | |
# Get the private upload URL. | |
$requestBody = @{ apikey = $apiKey } | |
$response = Invoke-RestMethod -Method Get -TimeoutSec $requestTimeout -Uri ($serviceUrl+'file/scan/upload_url') -Body $requestBody | |
$uploadUrl = $response.upload_url | |
$request = [Net.WebRequest]::CreateHttp($uploadUrl) | |
$request.Method = 'POST' | |
$boundaryId = [Guid]::NewGuid().ToString('N') | |
$request.ContentType = 'multipart/form-data; boundary=' + $boundaryId | |
$request.Timeout = $requestTimeout*1000 | |
$requestStream = $request.GetRequestStream() | |
$boundaryString = '--' + $boundaryId | |
WriteAsciiString $requestStream $boundaryString | |
WriteCRLF $requestStream | |
#Write the file content | |
WriteAsciiString $requestStream ('Content-Disposition: form-data; name="file"; filename="'+$([IO.Path]::GetFileName($packagePath))+'"') | |
WriteCRLF $requestStream | |
WriteAsciiString $requestStream 'Content-Type: application/octet-stream' | |
WriteCRLF $requestStream | |
WriteCRLF $requestStream | |
$file = New-Object System.IO.FileStream $packagePath, 'Open', 'Read' | |
$file.CopyTo($requestStream) | |
$file.Dispose() | |
WriteCRLF $requestStream | |
WriteAsciiString $requestStream $boundaryString | |
WriteAsciiString $requestStream '--' | |
WriteCRLF $requestStream | |
$requestStream.Dispose() | |
$response = [Net.HttpWebResponse] $request.GetResponse() | |
$responseStreamReader = New-Object System.IO.StreamReader $response.GetResponseStream() | |
$scanInfo = $responseStreamReader.ReadToEnd() | ConvertFrom-Json | |
$responseStreamReader.Dispose(); | |
} | |
Catch | |
{ | |
Write-Output $_.Exception | |
exit 1 | |
} | |
Write-Output 'Scan response:' | |
Write-Output $scanInfo | |
If ( $scanInfo.response_code -ne 1 ) | |
{ | |
Write-Output "Error: unexpected response_code=$($scanInfo.response_code)" | |
Exit 1 | |
} | |
# Retrieve the scan results - repeat until the scan is complete. | |
$progress = 0 | |
Do | |
{ | |
Try | |
{ | |
$requestBody = @{ resource = $scanInfo.scan_id; apikey = $apiKey } | |
$response = Invoke-RestMethod -Method Post -Uri ($serviceUrl+'file/report') -TimeoutSec $requestTimeout -Body $requestBody | |
If ( ($scanResponsePath -ne $null) -and ($scanResponsePath -ne '') ) | |
{ | |
$response | Out-File $scanResponsePath | |
} | |
} | |
Catch | |
{ | |
Write-Output $_.Exception | |
Exit 2 | |
} | |
Write-Output 'Scan report response:' | |
Write-Output $response | |
If ( $response.response_code -eq -2 ) | |
{ | |
Write-Output 'response_code=-2, waiting before another retry...' | |
Start-Sleep -s 15 | |
} | |
ElseIf ( $response.response_code -ne 1 ) | |
{ | |
Write-Output "Error: unexpected response_code=$($response.response_code)" | |
Exit 2 | |
} | |
} Until ( $response.response_code -eq 1 ) | |
# Check the received scan results. | |
$exitCode = 0 | |
If ( $response.positives -eq 0 ) | |
{ | |
Write-Output 'Success. No threats have been found.' | |
exit $exitCode | |
} | |
$config = Invoke-WebRequest $configFileUrl | ConvertFrom-Json | |
$engines = $response.scans | Get-Member -MemberType properties | Select-Object -Property Name | |
ForEach ( $engine in $engines ) | |
{ | |
$engineResult = $response.scans.$($engine.Name) | |
If ( $engineResult.detected ) | |
{ | |
# Known threat has been detected. | |
$ignoredThreats = $config.ignoredThreats.$($engine.Name) | |
If ( $engineResult.result -in $ignoredThreats ) | |
{ | |
Write-Output "$($engine.Name) detected threat: $($engineResult.result) [IGNORED]" | |
} | |
Else | |
{ | |
Write-Output "$($engine.Name) detected threat: $($engineResult.result)" | |
$exitCode = 3 | |
} | |
} | |
} | |
Write-Output "Scan results URL: $($response.permalink)" | |
exit $exitCode |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment