// question

Suspicious PowerShell activity on a Windows machine shows an external script being fetched and executed, followed by a quiet SQL enumeration call. Using process event logs, identify the name of the tool executed immediately after the script retrieval.

Reference KQL answer and model-by-model comparison.

0bb64470-582a-4155-bde2-d6003a95ed34
question id
1/23
models correct
1
valid answer
4%
success rate
// reference answer

Human-validated correct answer for this question.

powerSQL
// model comparison
ModelAnswerKQL queryStatusTriesTimeCost
gemini-2.5-flash-preview-04-17
no answer
no query generatedfail50.00s0.0573
gpt-35-turbo
no answer
no query generatedfail50.00s0.0113
gpt-4-turbo-2024-04-09
no answer
no query generatedfail50.00s0.2544
gpt-4.1
no answer
no query generatedfail50.00s0.0510
gpt-4.1-finetuned
no answer
no query generatedfail50.00s0.0496
gpt-4.1-mini
sshd.exe
DeviceProcessEvents
| where ProcessCommandLine has_any ("Invoke-WebRequest", "wget", "curl") and FileName == "powershell.exe"
| project DeviceId, InitiatingProcessSessionId, TimeGenerated, ProcessId, ProcessCommandLine
| join kind=inner (
    DeviceProcessEvents
    | project DeviceId, InitiatingProcessSessionId, TimeGenerated, ProcessId, FileName
) on DeviceId, InitiatingProcessSessionId
| where TimeGenerated > TimeGenerated1
| summarize min_Time = min(TimeGenerated1) by ProcessId1 = ProcessId, FileName, DeviceId, InitiatingProcessSessionId
| order by min_Time asc
| take 1
| project ToolExecutedAfterScriptRetrieval = FileName
fail13.73s0.0023
gpt-4.1-nano
powershell.exe
let suspicious_ps = DeviceProcessEvents
| where FileName endswith '.ps1' or FileName endswith '.psm1' or tolower(FileName) contains 'powershell' or tolower(FileName) contains 'pwsh'
| extend cmdline = tolower(ProcessCommandLine)
| where cmdline contains 'invoke-webrequest' or cmdline contains 'invoke-restmethod' or cmdline contains 'downloadstring' or cmdline contains 'bitstransfer'
| project InitiatingProcessId, InitiatingProcessFileName, ProcessCommandLine, TimeGenerated;
let fetch_end_time = suspicious_ps
| summarize max(TimeGenerated) by InitiatingProcessId;
DeviceProcessEvents
| where InitiatingProcessId in (suspicious_ps | distinct InitiatingProcessId)
| join kind=inner (
    fetch_end_time
) on $left.InitiatingProcessId == $right.InitiatingProcessId
| where TimeGenerated > max_TimeGenerated
| sort by TimeGenerated asc
| take 1
| project NextProcessName=InitiatingProcessFileName, TimeGenerated, InitiatingProcessId
fail417.28s0.0014
gpt-4o
powershell.exe
let powershell_activity = DeviceProcessEvents
| where InitiatingProcessFileName has "powershell.exe" and ProcessCommandLine contains "http"
| project DeviceId, PowershellProcessCreationTime = ProcessCreationTime;
DeviceProcessEvents
| join kind=inner (powershell_activity) on DeviceId
| where ProcessCreationTime > PowershellProcessCreationTime
| summarize ToolName = any(FileName), MinTime = min(ProcessCreationTime) by DeviceId
| order by MinTime asc
fail431.69s0.0565
gpt-5-high
csc.exe
let lookahead = 10m;
let ps_fetch =
DeviceProcessEvents
| where FileName in~ ('powershell.exe','pwsh.exe','powershell_ise.exe')
| extend cmd = tolower(tostring(ProcessCommandLine))
| where cmd has_any ('invoke-webrequest','iwr','wget','curl','start-bitstransfer','bitsadmin','downloadstring','downloadfile','net.webclient','invoke-restmethod','irm','new-object system.net.webclient')
| where cmd has_any ('iex','invoke-expression')
| project DeviceId, DeviceName, AccountDomain, AccountName, PSProcessId=ProcessId, PSProcessCreationTime=ProcessCreationTime, PSCommand=ProcessCommandLine;
let children =
DeviceProcessEvents
| project DeviceId, ChildTime=ProcessCreationTime, ChildFileName=FileName, ChildCommandLine=ProcessCommandLine, ParentPid=InitiatingProcessId, ParentCreationTime=InitiatingProcessCreationTime, ChildPid=ProcessId;
ps_fetch
| join kind=inner children on DeviceId
| where ParentPid == PSProcessId
| where ParentCreationTime between (PSProcessCreationTime - 1s .. PSProcessCreationTime + 1s)
| where ChildTime between (PSProcessCreationTime .. PSProcessCreationTime + lookahead)
| summarize arg_min(ChildTime, *) by DeviceId, DeviceName, PSProcessId, PSProcessCreationTime, PSCommand, AccountDomain, AccountName
| extend ExecutedTool = ChildFileName, ExecutedToolCommandLine = ChildCommandLine, ExecutedTime = ChildTime
| extend cmd_lc = tolower(tostring(ExecutedToolCommandLine)), tool_lc = tolower(tostring(ExecutedTool))
| extend IsLikelySqlEnum = iif(tool_lc in ('sqlcmd.exe','osql.exe','sqlplus.exe','isql.exe','sqlps.exe') or cmd_lc has_any ('invoke-sqlcmd','powerupsql',' -q ',' -Q ','select ','sp_help','xp_cmdshell'), true, false)
| project DeviceName, ExecutedTime, ExecutedTool, ExecutedToolCommandLine, FetchTime=PSProcessCreationTime, PSCommand, AccountDomain, AccountName, IsLikelySqlEnum
| order by FetchTime desc
fail3282.68s0.1795
gpt-5-mini-high
csc.exe
let fetchers = DeviceProcessEvents
| where FileName has_cs "powershell" or ProcessCommandLine has_cs "powershell" or ProcessCommandLine has_cs "pwsh"
| where ProcessCommandLine has_any ("Invoke-WebRequest", "Invoke-RestMethod", "IEX", "iex", "-EncodedCommand", "-enc", "-Command", "-c", "Start-Process", "-File")
| extend FetchTime = ProcessCreationTime
| project DeviceId, DeviceName, InitiatingProcessId, ProcessId, ProcessCommandLine, FileName, ProcessCreationTime, FetchTime;

fetchers
| join kind=inner (
    DeviceProcessEvents
    | where ProcessCreationTime >= ago(7d)
    | project DeviceId, DeviceName, ChildProcessId = ProcessId, ChildFileName = FileName, ChildCommandLine = ProcessCommandLine, ChildCreationTime = ProcessCreationTime, InitiatingProcessId = InitiatingProcessId
) on DeviceId
| where ChildCreationTime >= FetchTime and ChildCreationTime <= FetchTime + 2m
| where ChildProcessId != ProcessId
| summarize arg_min(ChildCreationTime, *) by DeviceId, FetchTime, ProcessId
| project DeviceName, DeviceId, FetchTime, PowerShellProcessId = ProcessId, PowerShellCmdLine = ProcessCommandLine, ToolExecuted = ChildFileName, ToolCmdLine = ChildCommandLine, ToolCreationTime = ChildCreationTime
| order by FetchTime desc
| take 50
fail130.22s0.0069
gpt-5-mini-low
powershell
let suspicious_ps = DeviceProcessEvents
| where FileName has_cs "powershell" or ProcessCommandLine has_cs "powershell"
| where ProcessCommandLine has_any ("IEX","Invoke-Expression","DownloadString","DownloadFile","Invoke-WebRequest","Invoke-RestMethod","curl","wget","-EncodedCommand","-e","-Command","-c")
| project DeviceName, PsTime=ProcessCreationTime, PsProcessId=ProcessId, PsCommandLine=ProcessCommandLine, PsFileName=FileName, InitiatingProcessId=InitiatingProcessId;

suspicious_ps
| join kind=inner (
    DeviceProcessEvents
    | project DeviceName, Time=ProcessCreationTime, ProcessId, FileName, ProcessCommandLine, InitiatingProcessId
) on DeviceName
| where Time > PsTime and Time <= PsTime + 2m
| where ProcessId != PsProcessId
| summarize arg_min(Time, *) by PsProcessId, PsTime, DeviceName, PsCommandLine
| project DeviceName, PsTime, PsProcessId, PsCommandLine, SubsequentProcessName=FileName, SubsequentProcessId=ProcessId, SubsequentProcessCommandLine=ProcessCommandLine, SubsequentProcessTime=Time
| sort by PsTime asc
| take 50
fail122.33s0.0069
gpt-5-mini-medium
no answer
no query generatedfail50.00s0.0137
gpt-5-nano-high
no answer
no query generatedfail50.00s0.0118
gpt-5-nano-medium
no answer
no query generatedfail50.00s0.0096
grok-3-beta
SenseIR.exe
let TimeRange = ago(7d);
DeviceProcessEvents
| where TimeGenerated >= TimeRange
| where InitiatingProcessFileName =~ 'powershell.exe'
| where ProcessCommandLine has_any ('Invoke-WebRequest', 'DownloadString', 'http://', 'https://')
| project ScriptDownloadTime=ProcessCreationTime, DeviceId, PowerShellCommandLine=ProcessCommandLine, InitiatingProcessId
| join kind=inner (
    DeviceProcessEvents
    | where TimeGenerated >= TimeRange
    | project SubsequentProcessTime=ProcessCreationTime, DeviceId, SubsequentProcessName=FileName, SubsequentCommandLine=ProcessCommandLine, SubsequentProcessId=ProcessId
) on DeviceId
| where SubsequentProcessTime between (ScriptDownloadTime .. (ScriptDownloadTime + 5m))
| where SubsequentProcessTime > ScriptDownloadTime
| summarize FirstToolExecuted = min(SubsequentProcessName), SubsequentCommandLine = min(SubsequentCommandLine) by DeviceId, ScriptDownloadTime, PowerShellCommandLine
| project DeviceId, ScriptDownloadTime, PowerShellCommandLine, FirstToolExecuted, SubsequentCommandLine
| order by ScriptDownloadTime asc
fail115.70s0.0688
grok-3-mini-beta
nmap-7.80-setup.exe
DeviceProcessEvents
| where InitiatingProcessFileName =~ 'powershell.exe' and InitiatingProcessCommandLine matches regex @'\b(Invoke-WebRequest|curl|wget)\b.*'
| order by ProcessCreationTime asc
| take 1
| project ToolExecuted = FileName
fail230.80s0.0030
o1-high
powershell.exe
let suspiciousEvents = DeviceProcessEvents
| where ActionType == "ProcessCreated"
| where FileName =~ "powershell.exe"
| where ProcessCommandLine has "http"  // indicates external script fetch
| project DeviceId, SuspiciousTime=ProcessCreationTime;

DeviceProcessEvents
| where ActionType == "ProcessCreated"
| join kind=inner suspiciousEvents on DeviceId
| where ProcessCreationTime > SuspiciousTime
| order by ProcessCreationTime asc
| summarize arg_min(ProcessCreationTime, *) by DeviceId, SuspiciousTime
| project DeviceName,
          ToolExecutedImmediatelyAfter=FileName,
          CommandLine=ProcessCommandLine,
          ExecutionTime=ProcessCreationTime
fail257.06s0.3903
o1-low
nmap-7.80-setup.exe
// Replace or adjust the time filter as needed.
DeviceProcessEvents
| where FileName =~ "powershell.exe"
| where ProcessCommandLine has_any ("invoke-webrequest", "iex", "downloadstring", "curl", "bitsadmin", "http")
| project DeviceId,
          DeviceName,
          ParentProcessId = ProcessId,
          ParentStartTime = ProcessCreationTime,
          ParentCommand = ProcessCommandLine
// Join to find child processes created by this PowerShell process.
| join kind=inner (
    DeviceProcessEvents
    | where ActionType == "ProcessCreated"
    | project DeviceId,
              ChildProcessName = FileName,
              ChildCommandLine = ProcessCommandLine,
              ChildProcessId = ProcessId,
              ChildStartTime = ProcessCreationTime,
              InitiatingProcessId
) on DeviceId
| where InitiatingProcessId == ParentProcessId
      and ChildStartTime > ParentStartTime
| order by ChildStartTime asc
// For each PowerShell event, take the earliest (arg_min) child process event.
| summarize arg_min(ChildStartTime, *) by ParentProcessId, DeviceId
| project DeviceName,
          ParentCommand,
          ExecutedTool = ChildProcessName,
          ExecutedToolCommandLine = ChildCommandLine,
          ChildStartTime
fail170.66s0.5200
o3-high
conhost.exe
// 1) Locate PowerShell processes that appear to download and immediately execute a remote script
let SuspiciousPowerShell = DeviceProcessEvents
| where FileName =~ "powershell.exe"
| extend cmd = tostring(ProcessCommandLine), cmd_lc = tolower(ProcessCommandLine)
| where cmd_lc has_any ("invoke-webrequest","invoke_webrequest","wget ","curl ","downloadstring(","bitsadmin","http://","https://") // remote fetch
| where cmd_lc has_any ("iex","invoke-expression","-exec","-encodedcommand","-executionpolicy bypass") // execution hints
| project DeviceId, DeviceName, FetchTime = ProcessCreationTime, FetchProcId = ProcessId,
         FetchCmd = ProcessCommandLine, FetchParentId = InitiatingProcessId;

// 2) For each of those, find the very first child process launched after the fetch (within 10 minutes)
let ChildProcWindow = 10m;
DeviceProcessEvents
| where InitiatingProcessId in (SuspiciousPowerShell | project FetchProcId)
| join kind=inner (SuspiciousPowerShell) on $left.InitiatingProcessId == $right.FetchProcId and $left.DeviceId == $right.DeviceId
| where ProcessCreationTime > FetchTime and ProcessCreationTime <= FetchTime + ChildProcWindow
| summarize FirstChildTime = min(ProcessCreationTime) by DeviceId, DeviceName, FetchTime, FetchCmd, FetchProcId, ChildProcessId = ProcessId, ChildFileName = FileName, ChildCmd = ProcessCommandLine
| order by FetchTime desc
| project DeviceName, FetchTime, DownloaderCommand = FetchCmd, EnumerationStart = FirstChildTime,
         EnumerationTool = ChildFileName, EnumerationCommand = ChildCmd
fail1100.30s0.0461
o3-mini-high
nmap
DeviceProcessEvents
| sort by DeviceName, TimeGenerated asc
| extend Prev_Process = prev(InitiatingProcessFileName), Prev_Command = prev(InitiatingProcessCommandLine)
// Filter for events where the previous event was a PowerShell fetching an external script (assuming 'http' in the command line indicates an external script call)
| where isnotempty(Prev_Command) and tolower(Prev_Process) == 'powershell.exe' and Prev_Command contains "http"
| project DeviceName, ScriptRetrievalTime = prev(TimeGenerated), RetrievalCommand = Prev_Command, SuspiciousToolTime = TimeGenerated, SuspiciousTool = InitiatingProcessFileName, SuspiciousToolCommand = ProcessCommandLine
| summarize arg_min(SuspiciousToolTime, *) by DeviceName
fail130.42s0.0161
o3-mini-low
powershell
DeviceProcessEvents
| sort by DeviceName asc, ProcessCreationTime asc
| serialize
| extend PrevDeviceName = prev(DeviceName)
| extend PrevFileName = prev(FileName)
| extend PrevCmdLine = prev(ProcessCommandLine)
| extend PrevCreationTime = prev(ProcessCreationTime)
| where DeviceName == PrevDeviceName
| where isnotempty(PrevCmdLine) and PrevCmdLine contains "http" and tostring(PrevFileName) =~ "powershell.exe"
| project DeviceName,
          ScriptRetrievalTime = PrevCreationTime,
          ScriptRetrievalCmdLine = PrevCmdLine,
          ToolExecutedAfterScriptRetrieval = FileName,
          NextEventTime = ProcessCreationTime,
          NextEventCmdLine = ProcessCommandLine
| order by NextEventTime asc
fail246.47s0.0454
o4-mini-high
powerSQL
// 1) Identify PowerShell script-download events
let downloads = DeviceProcessEvents
| where FileName in ("powershell.exe", "pwsh.exe")
// look for download methods in the command line
| where ProcessCommandLine matches regex @"(?i)(Invoke-WebRequest|DownloadString|DownloadFile|-Url\s+https?://)"
| project DeviceId, DownloadPid = ProcessId, DownloadTime = TimeGenerated;

// 2) Find child processes launched by those PowerShell PIDs after the download
let childProcs = DeviceProcessEvents
| where InitiatingProcessParentId in (downloads | distinct DownloadPid)
| project DeviceId, ParentPid = InitiatingProcessParentId, ChildName = FileName, ChildTime = TimeGenerated, ChildCmd = ProcessCommandLine;

// 3) Join and pick the first child process per download
downloads
| join kind=inner childProcs on DeviceId, $left.DownloadPid == $right.ParentPid
| where ChildTime > DownloadTime
| summarize arg_min(ChildTime, ChildName, ChildCmd, ChildTime) by DownloadPid, DeviceId
| project ToolExecuted = ChildName, ExecutionTime = ChildTime, CommandLine = ChildCmd
pass162.90s0.0179
o4-mini-low
nmap-7.80-setup.exe
let scriptFetches = DeviceProcessEvents
| where FileName == "powershell.exe"
| where ProcessCommandLine has_any ("Invoke-WebRequest", "IWR", "wget", "curl")
| project FetchTime = TimeGenerated, FetchPID = ProcessId, DeviceId, DeviceName, FetchCommandLine = ProcessCommandLine;

let childProcesses = DeviceProcessEvents
| project ChildTime = TimeGenerated, InitiatingProcessId, ChildTool = FileName, ChildCommandLine = ProcessCommandLine, DeviceId;

scriptFetches
| join kind=inner (
    childProcesses
) on $left.FetchPID == $right.InitiatingProcessId, $left.DeviceId == $right.DeviceId
| where ChildTime > FetchTime
| summarize arg_min(ChildTime, ChildTool, ChildCommandLine) by DeviceName, DeviceId, FetchTime, FetchPID, FetchCommandLine
| project DeviceName,
          DeviceId,
          FetchTime,
          FetchCommandLine,
          ToolExecuted = ChildTool,
          ToolCommandLine = ChildCommandLine,
          ExecutionTime = ChildTime
| sort by FetchTime asc
fail298.61s0.0336