sync: auto-sync from HOWARD-HOME at 2026-06-21 10:42:33
Author: Howard Enos Machine: HOWARD-HOME Timestamp: 2026-06-21 10:42:33
This commit is contained in:
368
.claude/scripts/guruscan-agent-test.sh
Normal file
368
.claude/scripts/guruscan-agent-test.sh
Normal file
@@ -0,0 +1,368 @@
|
||||
#!/usr/bin/env bash
|
||||
# guruscan-agent-test.sh - Deploy GuruScan to a Windows GuruRMM agent and run an
|
||||
# end-to-end smoke test (full 3-engine chain, EICAR-seeded, clean mode), pulling
|
||||
# every log back for review.
|
||||
#
|
||||
# Phases:
|
||||
# prep - upload module to C:\GuruScan, Defender-exclude tool/test dirs,
|
||||
# download RKill+Emsisoft, fetch HitmanPro, seed EICAR, verify ready
|
||||
# scan - dispatch Invoke-GuruScan.ps1 -Headless (clean mode) and poll
|
||||
# collect - pull results.json + per-scanner logs into the repo
|
||||
# all - prep then scan then collect
|
||||
#
|
||||
# Usage:
|
||||
# bash guruscan-agent-test.sh <hostname|uuid> <prep|scan|collect|all>
|
||||
#
|
||||
# Mirrors the RMM plumbing in run-onboarding-diagnostic.sh (vault auth -> JWT ->
|
||||
# chunked base64 upload -> dispatch -> poll). EICAR is the standard harmless AV
|
||||
# test file; it is assembled on the endpoint (never written to this host) and
|
||||
# dropped only into a Defender-excluded folder so GuruScan's own engines are what
|
||||
# detect it.
|
||||
|
||||
set -u
|
||||
|
||||
TARGET="${1:-}"
|
||||
PHASE="${2:-all}"
|
||||
|
||||
if [ -z "$TARGET" ]; then
|
||||
echo "[ERROR] Usage: bash guruscan-agent-test.sh <hostname|uuid> <prep|scan|collect|all>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
VAULT="$REPO_ROOT/.claude/scripts/vault.sh"
|
||||
ALERT="$REPO_ROOT/.claude/scripts/post-bot-alert.sh"
|
||||
GS_DIR="$REPO_ROOT/projects/msp-tools/guru-scan"
|
||||
RESULTS_ROOT="$GS_DIR/test-results"
|
||||
RMM="http://172.16.3.30:3001"
|
||||
HITMANPRO_URL="https://dl.surfright.nl/HitmanPro_x64.exe"
|
||||
|
||||
_logerr() { bash "$REPO_ROOT/.claude/scripts/log-skill-error.sh" "guruscan-test" "$@" >/dev/null 2>&1 || true; }
|
||||
post_alert() { [ -f "$ALERT" ] && bash "$ALERT" "$1" >/dev/null 2>&1 || true; }
|
||||
|
||||
for tool in jq curl base64 split; do
|
||||
command -v "$tool" >/dev/null 2>&1 || { echo "[ERROR] Required tool not found: $tool" >&2; exit 1; }
|
||||
done
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Auth
|
||||
# ---------------------------------------------------------------------------
|
||||
RMM_EMAIL="$(bash "$VAULT" get-field infrastructure/gururmm-server.sops.yaml credentials.gururmm-api.admin-email 2>/dev/null)"
|
||||
RMM_PASS="$(bash "$VAULT" get-field infrastructure/gururmm-server.sops.yaml credentials.gururmm-api.admin-password 2>/dev/null)"
|
||||
if [ -z "$RMM_EMAIL" ] || [ "$RMM_EMAIL" = "null" ] || [ -z "$RMM_PASS" ]; then
|
||||
echo "[ERROR] Could not read GuruRMM credentials from vault" >&2
|
||||
_logerr "vault read of GuruRMM creds failed"
|
||||
exit 1
|
||||
fi
|
||||
TOKEN="$(curl -s -m 30 -X POST "$RMM/api/auth/login" -H "Content-Type: application/json" \
|
||||
--data-binary "$(jq -nc --arg e "$RMM_EMAIL" --arg p "$RMM_PASS" '{email:$e,password:$p}')" | jq -r '.token // empty')"
|
||||
if [ -z "$TOKEN" ]; then
|
||||
echo "[ERROR] RMM login failed" >&2; _logerr "RMM login failed"; exit 1
|
||||
fi
|
||||
echo "[OK] Authenticated to GuruRMM"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Resolve agent (uuid -> exact host -> partial host)
|
||||
# ---------------------------------------------------------------------------
|
||||
AGENTS="$(curl -s -m 30 "$RMM/api/agents" -H "Authorization: Bearer $TOKEN")"
|
||||
echo "$AGENTS" | jq -e 'type=="array"' >/dev/null 2>&1 || { echo "[ERROR] Could not list agents" >&2; exit 1; }
|
||||
|
||||
if echo "$TARGET" | grep -qiE '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'; then
|
||||
AGENT="$(echo "$AGENTS" | jq --arg id "$TARGET" '[.[]|select(.id==$id)]|.[0]//empty')"
|
||||
else
|
||||
AGENT="$(echo "$AGENTS" | jq --arg h "$TARGET" '[.[]|select((.hostname|ascii_downcase)==($h|ascii_downcase))]|.[0]//empty')"
|
||||
if [ -z "$AGENT" ] || [ "$AGENT" = "null" ]; then
|
||||
MATCHES="$(echo "$AGENTS" | jq --arg h "$TARGET" '[.[]|select(.hostname|ascii_downcase|contains($h|ascii_downcase))]')"
|
||||
COUNT="$(echo "$MATCHES" | jq 'length')"
|
||||
[ "$COUNT" = "1" ] && AGENT="$(echo "$MATCHES" | jq '.[0]')"
|
||||
if [ "$COUNT" != "1" ]; then
|
||||
echo "[ERROR] $COUNT agents match '$TARGET'. Be more specific." >&2
|
||||
echo "$MATCHES" | jq -r '.[]|" \(.hostname) (\(.os_type)) id=\(.id)"' >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
[ -z "$AGENT" ] || [ "$AGENT" = "null" ] && { echo "[ERROR] No agent matching '$TARGET'." >&2; exit 1; }
|
||||
|
||||
AGENT_ID="$(echo "$AGENT" | jq -r '.id')"
|
||||
AGENT_HOST="$(echo "$AGENT" | jq -r '.hostname')"
|
||||
AGENT_OS="$(echo "$AGENT" | jq -r '.os_type')"
|
||||
AGENT_STATUS="$(echo "$AGENT" | jq -r '.status // "unknown"')"
|
||||
echo "[OK] Agent: $AGENT_HOST ($AGENT_OS) status=$AGENT_STATUS id=$AGENT_ID"
|
||||
[ "$AGENT_OS" != "windows" ] && { echo "[ERROR] Windows-only. os_type=$AGENT_OS" >&2; exit 1; }
|
||||
|
||||
WORK_DIR="$(mktemp -d 2>/dev/null || echo "${TMPDIR:-/tmp}/guruscan-test-$$")"
|
||||
mkdir -p "$WORK_DIR"
|
||||
trap 'rm -rf "$WORK_DIR" 2>/dev/null || true' EXIT
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# dispatch_one <script-file> <timeout_seconds> <max_polls> -> echoes result JSON
|
||||
# ---------------------------------------------------------------------------
|
||||
dispatch_one() {
|
||||
local script_file="$1" to="$2" max_polls="${3:-72}"
|
||||
local payload_file resp cmd_id status result count
|
||||
payload_file="$WORK_DIR/payload.json"
|
||||
jq -nc --rawfile cmd "$script_file" --argjson to "$to" \
|
||||
'{command_type:"powershell", command:$cmd, timeout_seconds:$to}' > "$payload_file"
|
||||
resp="$(curl -s -m 30 -X POST "$RMM/api/agents/$AGENT_ID/command" \
|
||||
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
|
||||
--data-binary "@$payload_file")"
|
||||
cmd_id="$(echo "$resp" | jq -r '.command_id // empty')"
|
||||
[ -z "$cmd_id" ] && { echo "[ERROR] Dispatch failed: $resp" >&2; return 1; }
|
||||
count=0
|
||||
while [ $count -lt "$max_polls" ]; do
|
||||
result="$(curl -s -m 30 "$RMM/api/commands/$cmd_id" -H "Authorization: Bearer $TOKEN")"
|
||||
status="$(echo "$result" | jq -r '.status // empty')"
|
||||
case "$status" in
|
||||
completed|failed|cancelled|interrupted)
|
||||
printf '%s' "$cmd_id" > "$WORK_DIR/last_cmd_id"
|
||||
echo "$result"; return 0 ;;
|
||||
*) count=$((count+1)); sleep 5 ;;
|
||||
esac
|
||||
done
|
||||
echo "[ERROR] Command $cmd_id did not finish (last=$status)" >&2; return 1
|
||||
}
|
||||
|
||||
# run_ps <script-file> <timeout> <max_polls> <label> -> prints status, returns nonzero on failure
|
||||
run_ps() {
|
||||
local sf="$1" to="$2" mp="$3" label="$4" res st ec out err
|
||||
res="$(dispatch_one "$sf" "$to" "$mp")" || { echo "[ERROR] $label: dispatch failed" >&2; return 1; }
|
||||
st="$(echo "$res" | jq -r '.status')"; ec="$(echo "$res" | jq -r '.exit_code // "null"')"
|
||||
out="$(echo "$res" | jq -r '.stdout // ""')"; err="$(echo "$res" | jq -r '.stderr // ""')"
|
||||
echo "--- $label: status=$st exit=$ec ---"
|
||||
[ -n "$out" ] && printf '%s\n' "$out"
|
||||
[ -n "$err" ] && { echo " [stderr]"; printf '%s\n' "$err" | head -30; }
|
||||
printf '%s' "$res" > "$WORK_DIR/last_result.json"
|
||||
[ "$st" = "completed" ] || return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
# upload_file <local> <remote_win_path> (chunked base64, decode on endpoint)
|
||||
upload_file() {
|
||||
local local_path="$1" remote="$2" b64="$WORK_DIR/up.b64" chunk_dir="$WORK_DIR/chunks"
|
||||
local remote_b64="${remote}.b64"
|
||||
[ -f "$local_path" ] || { echo "[ERROR] missing local file: $local_path" >&2; return 1; }
|
||||
rm -rf "$chunk_dir"; mkdir -p "$chunk_dir"
|
||||
if base64 -w0 "$local_path" > "$b64" 2>/dev/null; then :
|
||||
elif base64 -i "$local_path" > "$b64" 2>/dev/null; then :
|
||||
else base64 < "$local_path" | tr -d '\n' > "$b64"; fi
|
||||
split -b 24000 "$b64" "$chunk_dir/chunk_"
|
||||
local chunks idx=0 nch sf data
|
||||
chunks=$(ls -1 "$chunk_dir"/chunk_* | sort)
|
||||
nch=$(echo "$chunks" | wc -l | tr -d ' ')
|
||||
for ch in $chunks; do
|
||||
idx=$((idx+1)); data="$(cat "$ch")"; sf="$WORK_DIR/chunkcmd.ps1"
|
||||
if [ "$idx" -eq 1 ]; then
|
||||
cat > "$sf" <<PS
|
||||
\$ErrorActionPreference='Stop'
|
||||
\$d=Split-Path "$remote" -Parent
|
||||
if(-not (Test-Path \$d)){New-Item -ItemType Directory -Path \$d -Force|Out-Null}
|
||||
[System.IO.File]::WriteAllText("$remote_b64","$data")
|
||||
Write-Output "CHUNK $idx OK"
|
||||
PS
|
||||
else
|
||||
cat > "$sf" <<PS
|
||||
\$ErrorActionPreference='Stop'
|
||||
[System.IO.File]::AppendAllText("$remote_b64","$data")
|
||||
Write-Output "CHUNK $idx OK"
|
||||
PS
|
||||
fi
|
||||
local r; r="$(dispatch_one "$sf" 60)" || { echo "[ERROR] $(basename "$remote") chunk $idx dispatch failed" >&2; return 1; }
|
||||
[ "$(echo "$r" | jq -r '.status')" = "completed" ] || { echo "[ERROR] $(basename "$remote") chunk $idx failed" >&2; return 1; }
|
||||
done
|
||||
# decode
|
||||
local df="$WORK_DIR/decode.ps1"
|
||||
cat > "$df" <<PS
|
||||
\$ErrorActionPreference='Stop'
|
||||
\$b64=(Get-Content "$remote_b64" -Raw) -replace '\s',''
|
||||
[System.IO.File]::WriteAllBytes("$remote",[System.Convert]::FromBase64String(\$b64))
|
||||
Remove-Item "$remote_b64" -Force -ErrorAction SilentlyContinue
|
||||
\$len=(Get-Item "$remote").Length
|
||||
Write-Output ("WROTE $remote (\$len bytes)")
|
||||
PS
|
||||
local r; r="$(dispatch_one "$df" 60)" || { echo "[ERROR] decode dispatch failed for $remote" >&2; return 1; }
|
||||
[ "$(echo "$r" | jq -r '.status')" = "completed" ] || { echo "[ERROR] decode failed for $remote: $(echo "$r"|jq -r '.stderr'|head -c160)" >&2; return 1; }
|
||||
echo "[OK] uploaded $(basename "$remote") ($nch chunk(s)) -> $(echo "$r"|jq -r '.stdout'|tr -d '\r')"
|
||||
}
|
||||
|
||||
# ===========================================================================
|
||||
phase_prep() {
|
||||
echo ""; echo "=== PHASE: prep ==="
|
||||
# 1) upload module files
|
||||
local files="GuruScan.psm1 GuruScan.psd1 scanners.json Invoke-GuruScan.ps1 Invoke-ScannerCleanup.ps1 Download-Scanners.ps1"
|
||||
for f in $files; do
|
||||
upload_file "$GS_DIR/$f" "C:\\GuruScan\\$f" || { _logerr "upload failed" --context "file=$f host=$AGENT_HOST"; return 1; }
|
||||
done
|
||||
|
||||
# 2) Defender exclusions for tool + downloads + test dirs (so Defender does not
|
||||
# nuke the scanner EXEs or grab EICAR before GuruScan's engines run).
|
||||
local sf="$WORK_DIR/defender.ps1"
|
||||
cat > "$sf" <<'PS'
|
||||
$ErrorActionPreference='Continue'
|
||||
foreach($p in @('C:\GuruScan','C:\GuruScan\downloads','C:\GuruScanTest','C:\EmsisoftCmd')){
|
||||
try { Add-MpPreference -ExclusionPath $p -ErrorAction Stop; Write-Output "EXCLUDED $p" }
|
||||
catch { Write-Output ("EXCLUDE-SKIP " + $p + " : " + $_.Exception.Message) }
|
||||
}
|
||||
PS
|
||||
run_ps "$sf" 60 24 "defender-exclusions" || echo "[WARN] Defender exclusion step had issues (continuing)"
|
||||
|
||||
# 3) download RKill + Emsisoft via the module's downloader
|
||||
local sf2="$WORK_DIR/dl.ps1"
|
||||
cat > "$sf2" <<'PS'
|
||||
$ErrorActionPreference='Continue'
|
||||
& C:\GuruScan\Download-Scanners.ps1
|
||||
PS
|
||||
run_ps "$sf2" 600 130 "download-rkill-emsisoft" || echo "[WARN] scanner download reported issues (HitmanPro is expected MANUAL)"
|
||||
|
||||
# 4) fetch HitmanPro directly to downloads\
|
||||
local sf3="$WORK_DIR/hmp.ps1"
|
||||
cat > "$sf3" <<PS
|
||||
\$ErrorActionPreference='Continue'
|
||||
\$ProgressPreference='SilentlyContinue'
|
||||
\$dst='C:\\GuruScan\\downloads\\HitmanPro_x64.exe'
|
||||
try {
|
||||
[Net.ServicePointManager]::SecurityProtocol=[Net.ServicePointManager]::SecurityProtocol -bor 3072
|
||||
\$wc=New-Object System.Net.WebClient
|
||||
\$wc.Headers.Add('User-Agent','Mozilla/5.0 (Windows NT 10.0; Win64; x64)')
|
||||
\$wc.DownloadFile('$HITMANPRO_URL',\$dst)
|
||||
\$len=(Get-Item \$dst).Length
|
||||
Write-Output ("HITMANPRO OK \$len bytes")
|
||||
} catch { Write-Output ("HITMANPRO FAIL: " + \$_.Exception.Message) }
|
||||
PS
|
||||
run_ps "$sf3" 300 70 "download-hitmanpro" || echo "[WARN] HitmanPro download dispatch issue"
|
||||
|
||||
# 5) seed EICAR into the Defender-excluded test folder (assembled on endpoint)
|
||||
local sf4="$WORK_DIR/eicar.ps1"
|
||||
cat > "$sf4" <<'PS'
|
||||
$ErrorActionPreference='Continue'
|
||||
$dir='C:\GuruScanTest'
|
||||
if(-not (Test-Path $dir)){New-Item -ItemType Directory -Path $dir -Force|Out-Null}
|
||||
# Standard EICAR test signature, assembled from fragments so it is never stored
|
||||
# contiguously anywhere except the on-disk test file.
|
||||
$e = 'X5O!P%@AP[4\PZX54(P^)7CC)7}' + '$EICAR' + '-STANDARD-ANTIVIRUS-' + 'TEST-FILE!$H+H*'
|
||||
$f = Join-Path $dir 'eicar_test.com'
|
||||
Set-Content -Path $f -Value $e -Encoding ASCII -NoNewline
|
||||
Start-Sleep -Seconds 2
|
||||
if(Test-Path $f){
|
||||
$sz=(Get-Item $f).Length
|
||||
Write-Output ("EICAR seeded: $f ($sz bytes) - survived (Defender exclusion working)")
|
||||
} else {
|
||||
Write-Output "EICAR MISSING after write - Defender (or other AV) grabbed it despite exclusion"
|
||||
}
|
||||
PS
|
||||
run_ps "$sf4" 60 24 "seed-eicar" || { _logerr "EICAR seed failed" --context "host=$AGENT_HOST"; return 1; }
|
||||
|
||||
# 6) readiness check - confirm all three scanner binaries present + EICAR present
|
||||
local sf5="$WORK_DIR/ready.ps1"
|
||||
cat > "$sf5" <<'PS'
|
||||
$ErrorActionPreference='Continue'
|
||||
$need=@{
|
||||
'RKill' ='C:\GuruScan\downloads\rkill.exe';
|
||||
'Emsisoft' ='C:\GuruScan\downloads\EmsisoftCommandlineScanner64.exe';
|
||||
'HitmanPro' ='C:\GuruScan\downloads\HitmanPro_x64.exe';
|
||||
'Module' ='C:\GuruScan\GuruScan.psm1';
|
||||
'EICAR' ='C:\GuruScanTest\eicar_test.com'
|
||||
}
|
||||
$ok=$true
|
||||
foreach($k in $need.Keys){
|
||||
if(Test-Path $need[$k]){ Write-Output ("READY {0,-10} {1} ({2} bytes)" -f $k,$need[$k],(Get-Item $need[$k]).Length) }
|
||||
else { Write-Output ("MISSING {0,-10} {1}" -f $k,$need[$k]); $ok=$false }
|
||||
}
|
||||
if($ok){Write-Output 'ALL-READY'}else{Write-Output 'NOT-READY'}
|
||||
PS
|
||||
run_ps "$sf5" 60 24 "readiness" || return 1
|
||||
if grep -q 'ALL-READY' "$WORK_DIR/last_result.json" 2>/dev/null || \
|
||||
echo "$(jq -r '.stdout' "$WORK_DIR/last_result.json" 2>/dev/null)" | grep -q 'ALL-READY'; then
|
||||
echo "[OK] prep complete - endpoint is READY for scan"
|
||||
post_alert "[RMM] GuruScan test: prepped $AGENT_HOST (module+3 scanners+EICAR staged)"
|
||||
else
|
||||
echo "[WARN] prep finished but not all components READY - review output above before scanning"
|
||||
fi
|
||||
}
|
||||
|
||||
# ===========================================================================
|
||||
phase_scan() {
|
||||
echo ""; echo "=== PHASE: scan (clean mode, full chain, headless - LONG) ==="
|
||||
local sf="$WORK_DIR/scan.ps1"
|
||||
cat > "$sf" <<'PS'
|
||||
$ErrorActionPreference='Continue'
|
||||
& C:\GuruScan\Invoke-GuruScan.ps1 -Headless
|
||||
PS
|
||||
# Emsisoft timeout_min=120, HitmanPro=60. Give the command 2.5h and poll up to 3h.
|
||||
run_ps "$sf" 9000 2160 "guruscan-run" || { _logerr "GuruScan run failed" --context "host=$AGENT_HOST"; echo "[ERROR] scan phase failed"; return 1; }
|
||||
# surface the structured result line
|
||||
local out; out="$(jq -r '.stdout' "$WORK_DIR/last_result.json" 2>/dev/null)"
|
||||
echo ""
|
||||
echo "$out" | grep 'GURUSCAN_RESULT_JSON:' | sed 's/^GURUSCAN_RESULT_JSON://' | jq '.' 2>/dev/null \
|
||||
|| echo "[WARN] no GURUSCAN_RESULT_JSON line in stdout (see full output above)"
|
||||
local cmd_id; cmd_id="$(cat "$WORK_DIR/last_cmd_id" 2>/dev/null||echo ?)"
|
||||
post_alert "[RMM] GuruScan test: scan finished on $AGENT_HOST -> cmd:${cmd_id:0:8}"
|
||||
}
|
||||
|
||||
# ===========================================================================
|
||||
phase_collect() {
|
||||
echo ""; echo "=== PHASE: collect (pull results.json + logs) ==="
|
||||
local stamp; stamp="$(date -u +%Y%m%dT%H%M%S)"
|
||||
local outdir="$RESULTS_ROOT/${AGENT_HOST}-${stamp}"
|
||||
mkdir -p "$outdir"
|
||||
|
||||
# newest scan log dir + results.json + per-scanner logs, emitted as base64 blobs
|
||||
local sf="$WORK_DIR/collect.ps1"
|
||||
cat > "$sf" <<'PS'
|
||||
$ErrorActionPreference='Continue'
|
||||
$root='C:\ScanLogs'
|
||||
if(-not (Test-Path $root)){ Write-Output 'NO-SCANLOGS'; return }
|
||||
$dir=Get-ChildItem $root -Directory | Sort-Object LastWriteTime -Descending | Select-Object -First 1
|
||||
if(-not $dir){ Write-Output 'NO-SCAN-DIR'; return }
|
||||
Write-Output ("SCANDIR=" + $dir.FullName)
|
||||
Get-ChildItem $dir.FullName -File | ForEach-Object {
|
||||
$b=[Convert]::ToBase64String([IO.File]::ReadAllBytes($_.FullName))
|
||||
Write-Output ("===FILE===" + $_.Name + "===")
|
||||
Write-Output $b
|
||||
}
|
||||
Write-Output "===END==="
|
||||
PS
|
||||
run_ps "$sf" 180 72 "collect-logs" || { echo "[ERROR] collect failed"; return 1; }
|
||||
local out; out="$(jq -r '.stdout' "$WORK_DIR/last_result.json")"
|
||||
printf '%s' "$out" > "$WORK_DIR/collect.out"
|
||||
|
||||
# split the marker-delimited base64 blobs into files
|
||||
awk -v outdir="$WORK_DIR/blobs" '
|
||||
BEGIN{ system("mkdir -p \"" outdir "\""); name="" }
|
||||
/^===FILE===/ { n=$0; sub(/^===FILE===/,"",n); sub(/===$/,"",n); name=n; next }
|
||||
/^===END===/ { name=""; next }
|
||||
name!="" { print > (outdir "/" name ".b64") }
|
||||
' "$WORK_DIR/collect.out"
|
||||
|
||||
if [ -d "$WORK_DIR/blobs" ]; then
|
||||
for bf in "$WORK_DIR/blobs"/*.b64; do
|
||||
[ -e "$bf" ] || continue
|
||||
local name; name="$(basename "$bf" .b64)"
|
||||
tr -d '\r\n' < "$bf" | base64 -d > "$outdir/$name" 2>/dev/null \
|
||||
&& echo "[OK] pulled $name ($(wc -c < "$outdir/$name") bytes)" \
|
||||
|| echo "[WARN] could not decode $name"
|
||||
done
|
||||
fi
|
||||
|
||||
# save raw stdout (incl. SCANDIR line) too
|
||||
printf '%s\n' "$out" | grep -E '^(SCANDIR=|NO-)' > "$outdir/_collect-meta.txt" 2>/dev/null || true
|
||||
|
||||
echo ""
|
||||
if [ -f "$outdir/results.json" ]; then
|
||||
echo "=== results.json ==="
|
||||
jq '.' "$outdir/results.json" 2>/dev/null || cat "$outdir/results.json"
|
||||
else
|
||||
echo "[WARN] results.json not among pulled files - check $outdir"
|
||||
fi
|
||||
echo ""
|
||||
echo "[OK] logs saved to: $outdir"
|
||||
}
|
||||
|
||||
case "$PHASE" in
|
||||
prep) phase_prep ;;
|
||||
scan) phase_scan ;;
|
||||
collect) phase_collect ;;
|
||||
all) phase_prep && phase_scan && phase_collect ;;
|
||||
*) echo "[ERROR] Unknown phase '$PHASE' (prep|scan|collect|all)" >&2; exit 1 ;;
|
||||
esac
|
||||
@@ -458,6 +458,118 @@ def cmd_push_set(client, args):
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_push_test(client, args):
|
||||
if not _gated(f"send test push event '{args.event_type}'", args.confirm):
|
||||
return 3
|
||||
extra, rc = _load_json_arg(args.extra_json, "extra-json")
|
||||
if rc:
|
||||
return rc
|
||||
result = client.send_test_push_event(args.event_type, extra=extra or None)
|
||||
_emit({"testEvent": args.event_type, "result": result}, args.json, _print_kv)
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_package_details(client, args):
|
||||
_emit(client.get_package_details(args.package_id), args.json, _print_kv)
|
||||
|
||||
|
||||
def cmd_report_create(client, args):
|
||||
extra, rc = _load_json_arg(args.extra_json, "extra-json")
|
||||
if rc:
|
||||
return rc
|
||||
if not _gated(f"create report '{args.name}'", args.confirm):
|
||||
return 3
|
||||
result = client.create_report(args.name, extra=extra or None)
|
||||
_emit({"createdReport": args.name, "result": result}, args.json, _print_kv)
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_report_links(client, args):
|
||||
_emit(client.get_report_links(args.id), args.json, _print_kv)
|
||||
|
||||
|
||||
def cmd_report_delete(client, args):
|
||||
if not _gated(f"delete report {args.id}", args.confirm):
|
||||
return 3
|
||||
_emit({"deletedReport": args.id, "result": client.delete_report(args.id)},
|
||||
args.json, _print_kv)
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_quarantine_remove(client, args):
|
||||
if not _gated(f"remove {len(args.items)} quarantine item(s)", args.confirm):
|
||||
return 3
|
||||
result = client.remove_quarantine_items(args.items)
|
||||
_emit({"removedQuarantine": args.items, "result": result}, args.json, _print_kv)
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_quarantine_restore(client, args):
|
||||
extra, rc = _load_json_arg(args.extra_json, "extra-json")
|
||||
if rc:
|
||||
return rc
|
||||
if not _gated(f"restore {len(args.items)} quarantine item(s)", args.confirm):
|
||||
return 3
|
||||
result = client.restore_quarantine_items(args.items, extra=extra or None)
|
||||
_emit({"restoredQuarantine": args.items, "result": result}, args.json, _print_kv)
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_custom_rules(client, args):
|
||||
_emit(client.list_custom_rules(page=args.page, per_page=args.per_page),
|
||||
args.json, _print_kv)
|
||||
|
||||
|
||||
def cmd_custom_rule_create(client, args):
|
||||
extra, rc = _load_json_arg(args.extra_json, "extra-json")
|
||||
if rc:
|
||||
return rc
|
||||
if not _gated(f"create custom rule '{args.name}'", args.confirm):
|
||||
return 3
|
||||
result = client.create_custom_rule(args.name, extra=extra or None)
|
||||
_emit({"createdRule": args.name, "result": result}, args.json, _print_kv)
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_custom_rule_delete(client, args):
|
||||
if not _gated(f"delete custom rule {args.id}", args.confirm):
|
||||
return 3
|
||||
_emit({"deletedRule": args.id, "result": client.delete_custom_rule(args.id)},
|
||||
args.json, _print_kv)
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_incident_status(client, args):
|
||||
fields, rc = _load_json_arg(args.set_json, "set-json")
|
||||
if rc:
|
||||
return rc
|
||||
if not _gated(f"change incident status (type={args.type})", args.confirm):
|
||||
return 3
|
||||
result = client.change_incident_status(args.type, fields)
|
||||
_emit({"incidentStatus": "changed", "result": result}, args.json, _print_kv)
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_incident_note(client, args):
|
||||
fields, rc = _load_json_arg(args.set_json, "set-json")
|
||||
if rc:
|
||||
return rc
|
||||
if not _gated(f"update incident note (type={args.type})", args.confirm):
|
||||
return 3
|
||||
result = client.update_incident_note(args.type, fields)
|
||||
_emit({"incidentNote": "updated", "result": result}, args.json, _print_kv)
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_monthly_usage(client, args):
|
||||
_emit(client.get_monthly_usage(), args.json, _print_kv)
|
||||
|
||||
|
||||
def cmd_integrations(client, args):
|
||||
_emit(client.get_configured_integrations(page=args.page, per_page=args.per_page),
|
||||
args.json, _print_kv)
|
||||
|
||||
|
||||
def cmd_packages(client, args):
|
||||
_emit(client.list_packages(), args.json, _print_package_table)
|
||||
|
||||
@@ -556,7 +668,9 @@ DESTRUCTIVE_RAW_PATTERNS = ("delete", "createuninstall", "createremove",
|
||||
"removefromblocklist", "assignpolicy",
|
||||
"setpushevent", "createaccount", "updateaccount",
|
||||
"configurenotif", "createcompany", "suspendcompany",
|
||||
"activatecompany", "setendpointlabel")
|
||||
"activatecompany", "setendpointlabel", "createreport",
|
||||
"createrestore", "createcustomrule", "changeincident",
|
||||
"updateincident", "sendtestpush")
|
||||
|
||||
|
||||
def _is_destructive_method(method: str) -> bool:
|
||||
@@ -754,6 +868,24 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
sub.add_parser("push-stats",
|
||||
help="Show push event service delivery stats.", parents=[common])
|
||||
|
||||
sp = sub.add_parser("package-details", help="Installation package detail.",
|
||||
parents=[common])
|
||||
sp.add_argument("package_id")
|
||||
|
||||
sub.add_parser("monthly-usage", help="Monthly license usage.", parents=[common])
|
||||
sp = sub.add_parser("integrations", help="List configured integrations.",
|
||||
parents=[common])
|
||||
sp.add_argument("--page", type=int, default=1)
|
||||
sp.add_argument("--per-page", type=int, default=100)
|
||||
|
||||
sp = sub.add_parser("custom-rules", help="List EDR custom rules.", parents=[common])
|
||||
sp.add_argument("--page", type=int, default=1)
|
||||
sp.add_argument("--per-page", type=int, default=100)
|
||||
|
||||
sp = sub.add_parser("report-links", help="Get a report's download links.",
|
||||
parents=[common])
|
||||
sp.add_argument("--id", required=True, help="reportId.")
|
||||
|
||||
sp = sub.add_parser("quarantine", help="List quarantine items for a company.",
|
||||
parents=[common])
|
||||
sp.add_argument("--company", required=True)
|
||||
@@ -919,6 +1051,59 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
help="JSON object of the notification settings to apply.")
|
||||
sp.add_argument("--confirm", action="store_true")
|
||||
|
||||
sp = sub.add_parser("push-test", help="Send a test push event (gated).",
|
||||
parents=[common])
|
||||
sp.add_argument("--event-type", required=True)
|
||||
sp.add_argument("--extra-json")
|
||||
sp.add_argument("--confirm", action="store_true")
|
||||
|
||||
sp = sub.add_parser("report-create", help="Create a report (gated).",
|
||||
parents=[common])
|
||||
sp.add_argument("--name", required=True)
|
||||
sp.add_argument("--extra-json", help="JSON: type, targetIds, recurrence, format...")
|
||||
sp.add_argument("--confirm", action="store_true")
|
||||
|
||||
sp = sub.add_parser("report-delete", help="Delete a report (gated).",
|
||||
parents=[common])
|
||||
sp.add_argument("--id", required=True, help="reportId.")
|
||||
sp.add_argument("--confirm", action="store_true")
|
||||
|
||||
sp = sub.add_parser("quarantine-remove",
|
||||
help="Delete quarantined items (gated).", parents=[common])
|
||||
sp.add_argument("--items", nargs="+", required=True,
|
||||
help="quarantineItemsIds.")
|
||||
sp.add_argument("--confirm", action="store_true")
|
||||
|
||||
sp = sub.add_parser("quarantine-restore",
|
||||
help="Restore quarantined items (gated).", parents=[common])
|
||||
sp.add_argument("--items", nargs="+", required=True,
|
||||
help="quarantineItemsIds.")
|
||||
sp.add_argument("--extra-json", help="JSON: addExclusionInPolicy, etc.")
|
||||
sp.add_argument("--confirm", action="store_true")
|
||||
|
||||
sp = sub.add_parser("custom-rule-create",
|
||||
help="Create an EDR custom rule (gated).", parents=[common])
|
||||
sp.add_argument("--name", required=True)
|
||||
sp.add_argument("--extra-json", help="JSON: settings, companyId, tags...")
|
||||
sp.add_argument("--confirm", action="store_true")
|
||||
|
||||
sp = sub.add_parser("custom-rule-delete",
|
||||
help="Delete an EDR custom rule (gated).", parents=[common])
|
||||
sp.add_argument("--id", required=True, help="ruleId.")
|
||||
sp.add_argument("--confirm", action="store_true")
|
||||
|
||||
sp = sub.add_parser("incident-status",
|
||||
help="Change an incident's status (gated).", parents=[common])
|
||||
sp.add_argument("--type", required=True, help="Incident type/category.")
|
||||
sp.add_argument("--set-json", required=True, help="JSON: id, status, ...")
|
||||
sp.add_argument("--confirm", action="store_true")
|
||||
|
||||
sp = sub.add_parser("incident-note",
|
||||
help="Update an incident note (gated).", parents=[common])
|
||||
sp.add_argument("--type", required=True, help="Incident type/category.")
|
||||
sp.add_argument("--set-json", required=True, help="JSON: id, note, ...")
|
||||
sp.add_argument("--confirm", action="store_true")
|
||||
|
||||
sp = sub.add_parser("push-set",
|
||||
help="Configure the push event service (gated).",
|
||||
parents=[common])
|
||||
@@ -966,6 +1151,20 @@ HANDLERS = {
|
||||
"push-stats": cmd_push_stats,
|
||||
"assign-policy": cmd_assign_policy,
|
||||
"push-set": cmd_push_set,
|
||||
"push-test": cmd_push_test,
|
||||
"package-details": cmd_package_details,
|
||||
"monthly-usage": cmd_monthly_usage,
|
||||
"integrations": cmd_integrations,
|
||||
"custom-rules": cmd_custom_rules,
|
||||
"custom-rule-create": cmd_custom_rule_create,
|
||||
"custom-rule-delete": cmd_custom_rule_delete,
|
||||
"incident-status": cmd_incident_status,
|
||||
"incident-note": cmd_incident_note,
|
||||
"report-create": cmd_report_create,
|
||||
"report-links": cmd_report_links,
|
||||
"report-delete": cmd_report_delete,
|
||||
"quarantine-remove": cmd_quarantine_remove,
|
||||
"quarantine-restore": cmd_quarantine_restore,
|
||||
"quarantine": cmd_quarantine,
|
||||
"blocklist": cmd_blocklist,
|
||||
"incidents": cmd_incidents,
|
||||
|
||||
@@ -900,6 +900,127 @@ class GravityZoneClient:
|
||||
params["subscribeToEventTypes"] = subscribe_event_types
|
||||
return self._jsonrpc_request("push", "setPushEventSettings", params)
|
||||
|
||||
def send_test_push_event(self, event_type: str, extra: Optional[dict] = None) -> Any:
|
||||
"""Send a test push event (push.sendTestPushEvent). Requires `eventType`
|
||||
(verified). Fires against the configured receiver — STATE-ADJACENT, gate
|
||||
at the call site behind --confirm."""
|
||||
params: dict = {"eventType": event_type}
|
||||
if extra:
|
||||
params.update(extra)
|
||||
return self._jsonrpc_request("push", "sendTestPushEvent", params)
|
||||
|
||||
# ======================================================================
|
||||
# PACKAGES (detail) — read
|
||||
# ======================================================================
|
||||
def get_package_details(self, package_id: str) -> dict:
|
||||
"""Installation package detail (packages.getPackageDetails). `packageId`
|
||||
required (verified)."""
|
||||
return self._jsonrpc_request(
|
||||
"packages", "getPackageDetails", {"packageId": package_id}
|
||||
) or {}
|
||||
|
||||
# ======================================================================
|
||||
# REPORTS (create / delete) — getReportsList + get_report_links above
|
||||
# ======================================================================
|
||||
def create_report(self, name: str, extra: Optional[dict] = None) -> Any:
|
||||
"""Create a report (reports.createReport). `name` required (verified);
|
||||
`type`, `targetIds`, recurrence/format etc. passed via `extra`.
|
||||
STATE-CHANGING — gate at the call site behind --confirm."""
|
||||
params: dict = {"name": name}
|
||||
if extra:
|
||||
params.update(extra)
|
||||
return self._jsonrpc_request("reports", "createReport", params)
|
||||
|
||||
def delete_report(self, report_id: str) -> Any:
|
||||
"""Delete a report (reports.deleteReport). `reportId` required (verified).
|
||||
STATE-CHANGING — gate at the call site behind --confirm."""
|
||||
return self._jsonrpc_request(
|
||||
"reports", "deleteReport", {"reportId": report_id}
|
||||
)
|
||||
|
||||
# ======================================================================
|
||||
# QUARANTINE (remove / restore) — getQuarantineItemsList above
|
||||
# ======================================================================
|
||||
def remove_quarantine_items(
|
||||
self, quarantine_item_ids: list[str], extra: Optional[dict] = None
|
||||
) -> Any:
|
||||
"""Delete quarantined items (quarantine/computers.createRemoveQuarantineItemTask).
|
||||
`quarantineItemsIds` required (verified). STATE-CHANGING — gate behind --confirm."""
|
||||
params: dict = {"quarantineItemsIds": quarantine_item_ids}
|
||||
if extra:
|
||||
params.update(extra)
|
||||
return self._jsonrpc_request(
|
||||
"quarantine/computers", "createRemoveQuarantineItemTask", params
|
||||
)
|
||||
|
||||
def restore_quarantine_items(
|
||||
self, quarantine_item_ids: list[str], extra: Optional[dict] = None
|
||||
) -> Any:
|
||||
"""Restore quarantined items (quarantine/computers.createRestoreQuarantineItemTask).
|
||||
`quarantineItemsIds` required (verified). `addExclusionInPolicy` etc. via
|
||||
`extra`. STATE-CHANGING — gate behind --confirm."""
|
||||
params: dict = {"quarantineItemsIds": quarantine_item_ids}
|
||||
if extra:
|
||||
params.update(extra)
|
||||
return self._jsonrpc_request(
|
||||
"quarantine/computers", "createRestoreQuarantineItemTask", params
|
||||
)
|
||||
|
||||
# ======================================================================
|
||||
# INCIDENTS — custom rules + incident status/note (read + state-changing)
|
||||
# ======================================================================
|
||||
def list_custom_rules(self, page: int = 1, per_page: int = 100) -> dict:
|
||||
"""List EDR custom rules (incidents.getCustomRulesList). VERIFIED LIVE."""
|
||||
return self._jsonrpc_request(
|
||||
"incidents", "getCustomRulesList", {"page": page, "perPage": per_page}
|
||||
) or {}
|
||||
|
||||
def create_custom_rule(self, name: str, extra: Optional[dict] = None) -> Any:
|
||||
"""Create an EDR custom rule (incidents.createCustomRule). `name` required
|
||||
(verified); rule body (settings/companyId/tags) via `extra`.
|
||||
STATE-CHANGING — gate behind --confirm."""
|
||||
params: dict = {"name": name}
|
||||
if extra:
|
||||
params.update(extra)
|
||||
return self._jsonrpc_request("incidents", "createCustomRule", params)
|
||||
|
||||
def delete_custom_rule(self, rule_id: str) -> Any:
|
||||
"""Delete an EDR custom rule (incidents.deleteCustomRule). `ruleId` required
|
||||
(verified). STATE-CHANGING — gate behind --confirm."""
|
||||
return self._jsonrpc_request(
|
||||
"incidents", "deleteCustomRule", {"ruleId": rule_id}
|
||||
)
|
||||
|
||||
def change_incident_status(self, incident_type: str, fields: dict) -> Any:
|
||||
"""Change an incident's status (incidents.changeIncidentStatus). `type`
|
||||
required (verified) — the incident type/category — plus the incident id +
|
||||
target status in `fields`. STATE-CHANGING — gate behind --confirm."""
|
||||
params: dict = {"type": incident_type}
|
||||
params.update(fields or {})
|
||||
return self._jsonrpc_request("incidents", "changeIncidentStatus", params)
|
||||
|
||||
def update_incident_note(self, incident_type: str, fields: dict) -> Any:
|
||||
"""Update an incident note (incidents.updateIncidentNote). `type` required
|
||||
(verified) plus incident id + note text in `fields`. STATE-CHANGING."""
|
||||
params: dict = {"type": incident_type}
|
||||
params.update(fields or {})
|
||||
return self._jsonrpc_request("incidents", "updateIncidentNote", params)
|
||||
|
||||
# ======================================================================
|
||||
# LICENSING (usage) + INTEGRATIONS — read
|
||||
# ======================================================================
|
||||
def get_monthly_usage(self) -> dict:
|
||||
"""Monthly license usage (licensing.getMonthlyUsage). VERIFIED LIVE."""
|
||||
return self._jsonrpc_request("licensing", "getMonthlyUsage", {}) or {}
|
||||
|
||||
def get_configured_integrations(self, page: int = 1, per_page: int = 100) -> dict:
|
||||
"""Configured third-party integrations (integrations.getConfiguredIntegrations).
|
||||
VERIFIED LIVE."""
|
||||
return self._jsonrpc_request(
|
||||
"integrations", "getConfiguredIntegrations",
|
||||
{"page": page, "perPage": per_page},
|
||||
) or {}
|
||||
|
||||
# ======================================================================
|
||||
# CACHE LAYER (identity / structure only — never volatile status)
|
||||
# ======================================================================
|
||||
|
||||
@@ -105,6 +105,25 @@ check("push-set no confirm -> rc3", ["push-set", "--status", "1", "--url", "http
|
||||
check("push-set enable no url -> rc2", ["push-set", "--status", "1", "--confirm"], want_rc=2)
|
||||
check("raw assignPolicy no confirm -> rc3", ["raw", "--module", "network", "--method", "assignPolicy", "--params", "{}"], want_rc=3)
|
||||
|
||||
# --- remaining modules: reads ---
|
||||
check("monthly-usage", ["monthly-usage"], want_rc=0)
|
||||
check("integrations", ["integrations"], want_rc=0)
|
||||
check("custom-rules", ["custom-rules"], want_rc=0)
|
||||
check("custom-rules json", ["custom-rules", "--json"], want_rc=0, out_json_ok=True)
|
||||
|
||||
# --- remaining modules: gated writes (no-confirm -> rc3) ---
|
||||
check("push-test no confirm -> rc3", ["push-test", "--event-type", "av"], want_rc=3)
|
||||
check("report-create no confirm -> rc3", ["report-create", "--name", "R"], want_rc=3)
|
||||
check("report-delete no confirm -> rc3", ["report-delete", "--id", "x"], want_rc=3)
|
||||
check("quarantine-remove no confirm -> rc3", ["quarantine-remove", "--items", "x"], want_rc=3)
|
||||
check("quarantine-restore no confirm -> rc3", ["quarantine-restore", "--items", "x"], want_rc=3)
|
||||
check("custom-rule-create no confirm -> rc3", ["custom-rule-create", "--name", "R"], want_rc=3)
|
||||
check("custom-rule-delete no confirm -> rc3", ["custom-rule-delete", "--id", "x"], want_rc=3)
|
||||
check("incident-status no confirm -> rc3", ["incident-status", "--type", "t", "--set-json", "{}"], want_rc=3)
|
||||
check("incident-note no confirm -> rc3", ["incident-note", "--type", "t", "--set-json", "{}"], want_rc=3)
|
||||
check("raw createReport no confirm -> rc3", ["raw", "--module", "reports", "--method", "createReport", "--params", "{}"], want_rc=3)
|
||||
check("raw createCustomRule no confirm -> rc3", ["raw", "--module", "incidents", "--method", "createCustomRule", "--params", "{}"], want_rc=3)
|
||||
|
||||
# --- network completion ---
|
||||
check("endpoint-tags", ["endpoint-tags"], want_rc=0)
|
||||
check("set-label no confirm -> rc3", ["set-label", "--endpoint", "x", "--label", "y"], want_rc=3)
|
||||
|
||||
128
errorlog.md
128
errorlog.md
@@ -23,6 +23,134 @@ Categories (the `[type]` tag): _(none)_ = skill/command execution failure ·
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [network.getEndpointsList]: Invalid value for 'parentId' parameter. [ctx: cmd=endpoints]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [push.sendTestPushEvent]: The required parameter is missing : eventType [ctx: cmd=raw]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [incidents.updateIncidentNote]: The required parameter is missing : type [ctx: cmd=raw]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [incidents.changeIncidentStatus]: The required parameter is missing : type [ctx: cmd=raw]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [incidents.deleteCustomRule]: The required parameter is missing : ruleId [ctx: cmd=raw]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [incidents.createCustomRule]: The required parameter is missing : name [ctx: cmd=raw]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [quarantine/computers.createRestoreQuarantineItemTask]: The required parameter is missing : quarantineItemsIds [ctx: cmd=raw]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [quarantine/computers.createRemoveQuarantineItemTask]: The required parameter is missing : quarantineItemsIds [ctx: cmd=raw]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [packages.getPackageDetails]: The required parameter is missing : packageId [ctx: cmd=raw]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [integrations.getIntegrationsList]: The requested API method not found. [ctx: cmd=raw]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [licensing.getMonthlyUsagePerCompany]: The requested API method not found. [ctx: cmd=raw]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [push.sendTestPushEvent]: The required parameter is missing : eventType [ctx: cmd=raw]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [incidents.updateIncidentNote]: The required parameter is missing : type [ctx: cmd=raw]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [incidents.changeIncidentStatus]: The required parameter is missing : type [ctx: cmd=raw]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [incidents.createCustomRule]: The required parameter is missing : name [ctx: cmd=raw]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [quarantine/computers.createRestoreQuarantineItemTask]: The required parameter is missing : quarantineItemsIds [ctx: cmd=raw]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [quarantine/computers.createRemoveQuarantineItemTask]: The required parameter is missing : quarantineItemsIds [ctx: cmd=raw]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [reports.getReportConfiguration]: The requested API method not found. [ctx: cmd=raw]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [reports.deleteReport]: The required parameter is missing : reportId [ctx: cmd=raw]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [reports.getDownloadLinks]: The required parameter is missing : reportId [ctx: cmd=raw]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [reports.createReport]: The required parameter is missing : name [ctx: cmd=raw]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [packages.getPackageDetails]: The required parameter is missing : packageId [ctx: cmd=raw]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [policies.getPolicyDetails]: Invalid value for 'policyId' parameter. [ctx: cmd=policy]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [network.getManagedEndpointDetails]: Invalid value for 'endpointId' parameter. Expected format: 24-char hex ID [ctx: cmd=endpoint]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [network.getEndpointsList]: Invalid value for 'parentId' parameter. [ctx: cmd=endpoints]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [policies.getPolicyDetails]: Invalid value for 'policyId' parameter. [ctx: cmd=policy]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [network.getManagedEndpointDetails]: Invalid value for 'endpointId' parameter. Expected format: 24-char hex ID [ctx: cmd=endpoint]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [network.getEndpointsList]: Invalid value for 'parentId' parameter. [ctx: cmd=endpoints]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [policies.getPolicyDetails]: Invalid value for 'policyId' parameter. [ctx: cmd=policy]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [network.getManagedEndpointDetails]: Invalid value for 'endpointId' parameter. Expected format: 24-char hex ID [ctx: cmd=endpoint]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [network.getEndpointsList]: Invalid value for 'parentId' parameter. [ctx: cmd=endpoints]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [network.setEndpointLabel]: The required parameter is missing : label [ctx: cmd=raw]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [network.uninstallClientTask]: The requested API method not found. [ctx: cmd=raw]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [network.createUninstallRoleTask]: The requested API method not found. [ctx: cmd=raw]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [network.createUninstallClientTask]: The requested API method not found. [ctx: cmd=raw]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [network.createReconfigureClientTask]: The required parameter is missing : targetIds [ctx: cmd=raw]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [network.createUninstallTask]: The requested API method not found. [ctx: cmd=raw]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [network.createScanTaskByMailboxes]: The requested API method not found. [ctx: cmd=raw]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [network.getManagedEndpointDetailsByIp]: The requested API method not found. [ctx: cmd=raw]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [network.getEndpointsByPolicy]: The requested API method not found. [ctx: cmd=raw]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [network.setEndpointLabel]: The required parameter is missing : endpointId [ctx: cmd=raw]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [policies.getPolicyDetails]: Invalid value for 'policyId' parameter. [ctx: cmd=policy]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [network.getManagedEndpointDetails]: Invalid value for 'endpointId' parameter. Expected format: 24-char hex ID [ctx: cmd=endpoint]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [network.getEndpointsList]: Invalid value for 'parentId' parameter. [ctx: cmd=endpoints]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [companies.createCompany]: The required parameter is missing : name [ctx: cmd=raw]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [companies.activateCompany]: The required parameter is missing : companyId [ctx: cmd=raw]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [companies.deleteCompany]: The required parameter is missing : companyId [ctx: cmd=raw]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [companies.getCompaniesList]: The requested API method not found. [ctx: cmd=raw]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [companies.getCompanyDetailsByUser]: The required parameter is missing : username [ctx: cmd=raw]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [companies.suspendCompany]: The required parameter is missing : companyId [ctx: cmd=raw]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [companies.updateCompany]: The requested API method not found. [ctx: cmd=raw]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [companies.createCompany]: The required parameter is missing : type [ctx: cmd=raw]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [policies.getPolicyDetails]: Invalid value for 'policyId' parameter. [ctx: cmd=policy]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [network.getManagedEndpointDetails]: Invalid value for 'endpointId' parameter. Expected format: 24-char hex ID [ctx: cmd=endpoint]
|
||||
|
||||
2026-06-21 | Howard-Home | guruscan/Download-Scanners.ps1 | exit 1: 'The property Count cannot be found' under Set-StrictMode when $manual/$failed summary has a single row (.Count on scalar) [ctx: host=DESKTOP-MS42HNC stage=download-summary]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [network.getEndpointsList]: Invalid value for 'parentId' parameter. [ctx: cmd=endpoints]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [accounts.deleteAccount]: The required parameter is missing : accountId [ctx: cmd=raw]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [accounts.updateAccount]: The required parameter is missing : accountId [ctx: cmd=raw]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [accounts.createAccount]: The required parameter is missing : email [ctx: cmd=raw]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [policies.getPolicyDetails]: Invalid value for 'policyId' parameter. [ctx: cmd=policy]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [network.getManagedEndpointDetails]: Invalid value for 'endpointId' parameter. Expected format: 24-char hex ID [ctx: cmd=endpoint]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [network.getEndpointsList]: Invalid value for 'parentId' parameter. [ctx: cmd=endpoints]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [policies.getPolicyDetails]: Invalid value for 'policyId' parameter. [ctx: cmd=policy]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [network.getManagedEndpointDetails]: Invalid value for 'endpointId' parameter. Expected format: 24-char hex ID [ctx: cmd=endpoint]
|
||||
|
||||
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [network.getEndpointsList]: Invalid value for 'parentId' parameter. [ctx: cmd=endpoints]
|
||||
|
||||
2026-06-21 | GURU-KALI | remediation-tool/docs-drift | [friction] Mail.Send-already-in-suite kept resurfacing as 'broken/decision-needed' for 4 asks — root cause was gotchas.md saying 'suite has no mail scopes / mailbox BLOCKED' + a 'Decision 2026-06-15 NOT yet executed' block, contradicting feedback-memory line that the suite (exchange-op b43e7342) already holds Mail.Send. Fix: single authoritative truth across all live docs + headline in the feedback memory [ctx: ref=feedback_365_remediation_tool.md commit=f55b8d2]
|
||||
|
||||
2026-06-21 | GURU-KALI | mailbox/remediation-tool | [correction] assumed Mail.Send needs a separate app (fabb3421/Claude-MSP-Access); correct is Mail.Send ALREADY EXISTS in the 365 remediation app suite — docs hardwiring the deleted fabb3421 must be purged everywhere [ctx: ref=4th-time-asked]
|
||||
|
||||
Reference in New Issue
Block a user