Skip to content

Instantly share code, notes, and snippets.

@gfraiteur
Created December 22, 2020 07:44
Show Gist options
  • Save gfraiteur/ab8d59bd19e60e5ac49c4730775ad3ea to your computer and use it in GitHub Desktop.
Save gfraiteur/ab8d59bd19e60e5ac49c4730775ad3ea to your computer and use it in GitHub Desktop.
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