/** * Audit logic: hot paths (filesystem, processes, drives, system, network, env) → Python FastAPI. * Windows-specific shell/registry work stays in Node. */ import * as path from 'node:path' import * as os from 'node:os' import { execFile, execFileSync, spawn } from 'node:child_process' import { promisify } from 'node:util' import { pyGet, pyPost } from './pythonBackend' const execFileAsync = promisify(execFile) export interface DriveInfo { letter: string mount: string label: string totalBytes: number freeBytes: number usedBytes: number } export interface DirEntry { name: string fullPath: string isDirectory: boolean sizeBytes: number mtimeMs: number error?: string sizeTruncated?: boolean } export interface ProcessRow { pid: number name: string memoryBytes: number cpuSeconds?: number commandLine?: string } export interface ServiceRow { name: string displayName: string state: string startType: string } export interface InstalledApp { name: string version: string publisher: string installLocation: string uninstallString: string estimatedSizeKb: number } export interface NetworkRow { name: string address: string family: string internal: boolean mac?: string } function resolveSafePath(input: string): string { const normalized = path.normalize(input) return path.resolve(normalized) } export async function getDrivesWin(): Promise { return pyGet('/api/drives') } export async function listDirectory( dirPath: string, options: { maxEntries?: number } = {} ): Promise { const root = resolveSafePath(dirPath) return pyPost('/api/list_dir', { path: root, max_entries: options.maxEntries ?? 800, }) } export async function computeFolderSize(dirPath: string): Promise<{ bytes: number; files: number; truncated: boolean }> { return pyPost<{ bytes: number; files: number; truncated: boolean }>('/api/folder_size', { path: resolveSafePath(dirPath), }) } export async function findLargeFiles( rootPath: string, minBytes: number, maxResults: number ): Promise<{ path: string; sizeBytes: number }[]> { return pyPost<{ path: string; sizeBytes: number }[]>('/api/large_files', { path: resolveSafePath(rootPath), min_bytes: minBytes, max_results: maxResults, }) } export async function getProcessesWin(): Promise { return pyGet('/api/processes') } export async function getServicesWin(): Promise { const script = ` Get-CimInstance Win32_Service | Select-Object Name,DisplayName,State,StartMode | ConvertTo-Json -Compress ` try { const { stdout } = await execFileAsync( 'powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', script], { windowsHide: true, maxBuffer: 20 * 1024 * 1024, timeout: 120_000 } ) const raw = JSON.parse(stdout.trim() || '[]') const arr = Array.isArray(raw) ? raw : [raw] return arr.map((s: Record) => ({ name: String(s.Name ?? ''), displayName: String(s.DisplayName ?? ''), state: String(s.State ?? ''), startType: String(s.StartMode ?? ''), })) } catch { return [] } } export function getInstalledPrograms(): InstalledApp[] { const script = ` $paths = @( 'HKLM:\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*', 'HKLM:\\Software\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*', 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*' ) Get-ItemProperty $paths -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName } | Select-Object DisplayName, DisplayVersion, Publisher, InstallLocation, UninstallString, EstimatedSize | ConvertTo-Json -Compress -Depth 4 ` try { const stdout = execFileSync( 'powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', script], { encoding: 'utf8', windowsHide: true, maxBuffer: 50 * 1024 * 1024 } ) const raw = JSON.parse(stdout.trim() || '[]') const arr = Array.isArray(raw) ? raw : [raw] const apps: InstalledApp[] = arr.map((r: Record) => ({ name: String(r.DisplayName ?? ''), version: String(r.DisplayVersion ?? ''), publisher: String(r.Publisher ?? ''), installLocation: String(r.InstallLocation ?? ''), uninstallString: String(r.UninstallString ?? ''), estimatedSizeKb: Number(r.EstimatedSize) || 0, })) const seen = new Set() return apps .filter((a) => { const k = a.name.toLowerCase() if (!k || seen.has(k)) return false seen.add(k) return true }) .sort((a, b) => a.name.localeCompare(b.name)) } catch { return [] } } export async function getSystemSnapshot() { return pyGet<{ hostname: string platform: string release: string arch: string uptimeSec: number totalMem: number freeMem: number cpuModel: string cpuCount: number load1: number load5: number load15: number userInfo: string homedir: string tmpdir: string }>('/api/system') } export async function getNetworkInterfaces(): Promise { return pyGet('/api/network') } export async function getEnvSnapshot(keys?: string[]): Promise> { const all = await pyGet>('/api/env') if (!keys?.length) return all const out: Record = {} for (const k of keys) { const v = all[k] if (v !== undefined) out[k] = v } return out } export async function getStartupFolders(): Promise<{ path: string; entries: DirEntry[] }[]> { const appData = process.env.APPDATA ?? path.join(os.homedir(), 'AppData', 'Roaming') const programData = process.env.PROGRAMDATA ?? 'C:\\ProgramData' const folders = [ path.join(appData, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup'), path.join(programData, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'StartUp'), ] const result: { path: string; entries: DirEntry[] }[] = [] for (const f of folders) { const entries = await listDirectory(f, { maxEntries: 200 }) result.push({ path: f, entries }) } return result } export async function getTempAudit(): Promise<{ path: string; bytes: number; files: number; truncated: boolean }[]> { const dirs = [os.tmpdir(), path.join(os.tmpdir(), '..', 'Temp')].map((p) => path.normalize(p)) const uniq = [...new Set(dirs)] const out: { path: string; bytes: number; files: number; truncated: boolean }[] = [] for (const d of uniq) { try { const r = await computeFolderSize(d) out.push({ path: d, ...r }) } catch { out.push({ path: d, bytes: 0, files: 0, truncated: false }) } } return out } export async function getScheduledTasksSummary(): Promise<{ name: string; state: string }[]> { const script = ` Get-ScheduledTask | Select-Object TaskName,State | ConvertTo-Json -Compress ` try { const { stdout } = await execFileAsync( 'powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', script], { windowsHide: true, maxBuffer: 20 * 1024 * 1024, timeout: 120_000 } ) const raw = JSON.parse(stdout.trim() || '[]') const arr = Array.isArray(raw) ? raw : [raw] return arr.map((t: Record) => ({ name: String(t.TaskName ?? ''), state: String(t.State ?? ''), })) } catch { return [] } } export async function openPathInExplorer(p: string): Promise { const resolved = resolveSafePath(p) await execFileAsync('explorer.exe', [resolved], { windowsHide: true }) } export function killProcess(pid: number): Promise { return new Promise((resolve, reject) => { const proc = spawn('taskkill', ['/PID', String(pid), '/F'], { windowsHide: true }) proc.on('close', (code) => (code === 0 ? resolve() : reject(new Error(`taskkill exit ${code}`)))) proc.on('error', reject) }) } export async function getWindowsFeaturesSnippet(): Promise { try { const { stdout } = await execFileAsync( 'dism.exe', ['/Online', '/Get-Features', '/Format:Table'], { windowsHide: true, maxBuffer: 5 * 1024 * 1024, timeout: 60_000 } ) return stdout.slice(0, 120_000) } catch (e) { return String((e as Error).message) } }