A look under the hood at how KillerShell walks a folder tree, matches filenames, reads inside files and archives, moves them around, hosts a shell, edits text, maps a disk and manages processes, performance, events and the registry - all without ever building an index.
Tech stack
KillerShell is a native Windows app. There is no Electron, no browser engine, no runtime to install, and no background service. Everything below ships inside a single .exe that runs on its own:
| Component | Detail |
|---|---|
| UI | WPF on .NET Framework 4.8 (net48), x64, custom window chrome |
| Search engine | A parallel engine: one walker feeds a worker per CPU core, and results are pushed to the UI in batches, cancelable at any time |
| File walk | A breadth-first walk built on Directory.EnumerateFiles, yielding files one at a time and skipping folders it cannot read |
| Content read | A buffered FileStream and StreamReader that reads each file line by line, with a null-byte check to skip binaries |
| Archives | Zip and gzip out of .NET Framework itself and a tar reader written in place, so a .zip or a .tar browses like a folder without adding a single dependency |
| Disk map | Direct NTFS Master File Table enumeration when elevated, with a parallel FindFirstFileExW fallback, feeding a squarified treemap drawn in one render pass |
| Export | A self-contained HTML report or CSV, written directly from the result set or the storage scan, no libraries required |
| Terminal | A hosted console: the shell you picked runs as a real child process and its output is drawn by the app, with the working directory tracked from the shell's own OSC 7 reports |
| Editor | AvalonEdit (MIT), vendored as source and compiled into the exe rather than shipped as a DLL, so the app is still one file |
| Packaging | Single executable. Run it portable, or install it for your account or for every user on the PC. |
Install & data
KillerShell is a single .exe. Put it anywhere and run it as is, or use the built-in installer, which copies the app in, adds a Start Menu shortcut, and registers a normal Windows uninstall entry. Installing for your own account needs no admin rights; installing for every user on the PC asks for permission once and puts it in Program Files, which is the same machine-wide install /silent performs for winget, Chocolatey and RMM tools. Either way there is no .NET runtime and nothing else to install.
Your settings, such as the last folder you searched and your theme choice, are kept in the registry under your own account rather than in a file next to the .exe, so the program stays a clean single file you can move around. When you run it from outside its installed location, a PORTABLE badge appears so you know which copy you are using.
The search pipeline
A search is one continuous pass over a folder tree. You give KillerShell a starting folder and one or more search terms, then it walks every file underneath and tests each one against your terms as it goes. There is no separate indexing step and nothing is stored between runs, so the first search on a folder is just as fast as the tenth.
Each term is either a name term, matched against the filename, or a text term, matched against the contents of the file. You can mix as many of each as you like in a single pass, and optional include and exclude filters narrow which files are looked at in the first place. A piped tab - search within results - runs this same pass over a snapshot of another tab's result list instead of walking a folder.
Matching files
Every file that clears the filters is tested against each of your terms. The two kinds of term work differently.
Name terms: wildcards
A name term is a wildcard pattern tested against the filename. An asterisk stands for any run of characters and a question mark stands for a single character, the same shorthand Windows itself uses. The pattern is turned into an anchored regular expression once, so *.log matches any name ending in ".log" and report_?.txt matches "report_1.txt" but not "report_final.txt".
report_*.xlsx // report_ then anything, then .xlsx
?.tmp // a single character, then .tmp
Text terms: reading inside files
A text term looks for a run of characters inside the file. Before reading, KillerShell checks the first few kilobytes for a null byte, which almost never appears in text but is common in programs, images, and archives. If it finds one, the file is treated as binary and skipped, so a content search does not waste time scanning a photo or an .exe.
Files that pass that check are read one line at a time through a buffered reader that also honors a byte-order mark if the file has one. The whole file is never loaded into memory at once, so a multi-gigabyte log is no heavier to search than a small note. Each line that contains the term is recorded with its line number and its text, trimmed of surrounding whitespace - up to the first 100 matched lines per file, so one pathological file cannot flood the results.
Filters & multiple terms
Include and exclude filters decide which files are looked at before any term is tested. Both take a list of patterns separated by semicolons, trimmed of spaces. Alongside them, filter rows narrow the pass by extension, date modified, or file size; a file must clear every active filter before any term is tested.
- Include patterns are wildcards tested against the filename. When any include is set, a file has to match at least one of them to be searched at all, so
*.txt;*.loglimits the pass to text and log files. - Exclude patterns work two ways at once. A pattern that matches a folder name anywhere in the path, such as
bin,obj, ornode_modules, drops everything under that folder. The same pattern is also tested as a wildcard against the filename, so*.min.jsskips minified files wherever they sit.
Every term you add runs in the same single pass over the tree, so searching for three names and two phrases costs one walk of the folder, not five. Each term keeps its own running match count, shown as a small badge next to it, so you can see at a glance which patterns are hitting and which are not.
Streaming, memory, and cancellation
The reason KillerShell can start on a huge folder like your whole user profile without a long wait is that it never builds a list of the tree up front. The walk yields one file at a time and the search reacts to each file as it arrives, so work begins on the first file instead of after the last. Folders it is not allowed to read are quietly stepped over rather than stopping the search.
The search itself runs on background workers, one per CPU core, which keeps the window responsive and lets a long search be canceled the moment you ask. Results are collected and handed to the UI in batches on a fixed timer, about once every 150 milliseconds, along with the running file count and the file currently being read. That single, gentle pace is what stops a fast search from flooding the interface with thousands of tiny updates.
Memory stays flat because nothing large is ever held at once. Files are read through fixed 64 KB buffers, content is streamed line by line, and only the lines that actually matched are kept. A search across hundreds of thousands of files uses about the same memory as a search across a handful.
Export
Any result set can be written to a self-contained HTML report: the matched files, the matched lines with their line numbers, and the search that produced them. The report is a single file that opens in any browser with no dependencies, which makes it easy to attach to a ticket or keep as a record of what was found and where. A CSV export sits alongside it for spreadsheets. A Storage Analyzer scan exports the same way: a single HTML file carrying the current filters, an SVG treemap, ranked folder and file tables, and a file-type color legend.
File operations
Copy and move run on a background worker with a progress card showing the current file and a real byte count, so a large copy never freezes the window. A name collision at the destination stops the worker and asks, showing both files' size and date, with Replace, Skip or Keep both and a "do the same for the rest" checkbox. The worker genuinely waits on that answer rather than copying ahead of it, so nothing is overwritten while the question is still on screen.
A move within one volume is performed as a rename, which is a directory-entry change rather than a data copy, so it is instant whatever the size. Across volumes it copies first and deletes the source only once the copy has landed. Sending to the Recycle Bin is the one operation that goes through the Windows shell, because there is no other way in, and that is also what puts it on Explorer's undo stack, so Ctrl+Z in Explorer brings the files back.
Drops onto a browsed folder follow Explorer's modifier rules exactly: Shift moves, Ctrl copies, and neither means move within a drive and copy across drives. Files already sitting in the target folder are ignored rather than raising a collision with themselves, and a folder cannot be dropped into its own child.
Archives
No new dependencies
A .zip, .tar, .tar.gz, .tgz or .gz opens as a folder, with the same rows, sorting, views and keys as anywhere else, and nothing was added to the app to do it. Zip and gzip ship inside .NET Framework and come in on two framework <Reference> lines rather than packages, so the zero-dependency single-exe build is exactly what it was. Tar is parsed here in about two hundred lines, because System.Formats.Tar is .NET 7 and later while KillerShell targets 4.8.
That reader takes every header flavor in circulation: v7, ustar, GNU long-name records and pax extended headers. pax is what bsdtar, macOS tar and Python's tarfile write by default, so a reader that knows only GNU silently truncates every long path written by anything modern. .rar, .7z, .bz2, .xz, .cab and .iso are neither read nor written; unrar's license forbids reimplementing RAR, so it would mean a dependency to embed.
Why the separator is a question mark
A location inside an archive is one string: the archive's own path, a separator, then the path within it, so a tab, a bookmark and the address bar go on treating a location as a single string. The question mark is the only character that satisfies both rules. It has to be illegal in a Windows filename, or a virtual path could collide with a real file; and it has to be legal to System.IO.Path, because these paths flow through Path.GetExtension all over the app. > and | pass the first and fail the second, so GetExtension throws - which killed every icon inside an archive the first time this was built.
Extraction treats every entry path as hostile. Archive paths are normalized to forward slashes with . and .. segments removed before they reach the browser, and opening an entry deliberately keeps only its leaf filename. That leaf is written inside a new randomly named temporary folder rather than combining the archive's directory path with a destination. A crafted entry such as ..\..\Startup\payload.exe therefore becomes an ordinary payload.exe in KillerShell's temporary extraction area instead of escaping it and overwriting something elsewhere on the machine - the Zip Slip attack.
Writing: zip only, never in place
A .zip is writable: add by drag or paste, rename, delete, New Folder. Tar, .tar.gz, .tgz and .gz are read-only deliberately - the tar reader models regular files and directories only, so a rebuild would silently drop links, devices, FIFOs and sparse members, and a lone .gz holds one unnamed member, so "add a second file" has no representation in the format. A refused write says which refusal it was on the status bar; it never no-ops and never half-writes.
ZipArchiveMode.Update is the obvious route and is deliberately not used: it holds the whole archive in memory while it is open, and rewrites the file in place on save. Every operation instead streams a complete new archive beside the original, proves it reopens with the expected entry count, and only then swaps it in, so a crash, a full disk or a cancel leaves the original untouched. Adding one file therefore rewrites the whole zip, which is why the status bar carries a percentage.
Out is a copy, by design
Dragging out of an archive is a copy, always. A move out is an extract plus a delete, two steps with no way to make them one, and a failure between them either loses the file or silently leaves it in place; Explorer treats zip drag-out the same way. Dragging a folder out extracts its whole tree, folders and all, wherever you let go.
The terminal
Running a real shell
A shell tab starts the host you picked as an ordinary child process with the folder you were looking at as its working directory, then draws its output itself. Nothing about the shell is emulated or wrapped: the process is a real PowerShell, Windows PowerShell or cmd.exe, so your modules, aliases and profile all behave the way they do anywhere else.
Tracking the working directory
The working directory shown on the shell's toolbar comes from the shell reporting it, not from KillerShell guessing. Shells emit an OSC 7 escape sequence naming their current directory, which is parsed out of the stream, so the readout stays correct through a cd, a script or a pushd, and clicking it opens that exact folder as a browsing tab.
Finding $PROFILE
The $PROFILE path is obtained by launching the shell binary with -NoProfile -NonInteractive and asking it for $PROFILE.CurrentUserCurrentHost, then validating that what came back is a single rooted path ending in .ps1. Assuming Documents\PowerShell\ instead would be wrong on any machine where OneDrive Known Folder Move has redirected Documents, which on a managed fleet is most of them, and the assumed path is a file the shell will never load.
Prompt glyphs & fallback fonts
The shipped prompt draws powerline separators, a git branch mark and a chevron. No monospaced font Windows ships has any of them, so the exe carries KillerGlyphs.ttf: 26 glyphs, 2,928 bytes, subset from Terminess Nerd Font under the SIL OFL. The renderer asks the font you chose for a glyph and only falls back to that face for the codepoints it does not have, stretching the fallback glyph to the chosen face's cell width so a separator still butts against the next cell. Your font choice is untouched; it just stops being able to fail on those characters. A whole Nerd Font would have cost 2.6 MB, almost all of it icons no prompt draws.
The editor
The editor is AvalonEdit, the component behind SharpDevelop, vendored into the repository as source and compiled into the exe rather than referenced as a DLL. That keeps KillerShell a single portable file and costs about 640 KB. The vendored tree was audited to zero compiler warnings on the way in, without suppressing any of them.
Encoding is decided by inspecting the file's bytes, not by the editor's own loader, which hands back a UTF-8 encoding that writes a byte-order mark. Left alone, a plain .txt or .bat would silently grow three bytes at the front the first time it was saved. A BOM that is present is kept, a BOM that is absent stays absent, and bytes that do not decode as UTF-8 fall back to the system ANSI codepage rather than being replaced with U+FFFD. Both the encoding and the line ending are shown under the document, because whether PowerShell 5.1 will read a script depends on both and neither is visible in the text.
Syntax definitions for .bat, .reg, .ini, .yaml, .log and .csv are hand-written and embedded, since AvalonEdit ships none of them. Rather than maintain a repainted copy of every definition for each of the thirteen themes, the shipped colors are run through the same contrast guard the terminal uses: hue is preserved and only lightness is moved, far enough to clear a readable contrast ratio against whatever the current background is. The original color is remembered per definition, so switching themes repeatedly cannot ratchet a color away from what its author intended.
Processes, storage, performance, events & the registry
Processes & services
Processes/Services (F9, elevated on Ctrl+F9) is one control switching between two grids, not two features bolted together. CPU% is computed as a delta between two TotalProcessorTime samples on a 1.5s tick, not a lifetime average, and command line, executable path and parent PID come from a single bulk Win32_Process WMI query per tick rather than one query per row. Process owner lookups are the expensive part - a per-PID WMI GetOwner() call - so each is done once, cached, and run on its own background thread rather than blocking the refresh; that used to freeze the tab on first load before the lookup was moved off the UI thread. Services mode cross-references ServiceController.GetServices() with a bulk Win32_Service query for the fields ServiceController does not expose, such as start mode, path and log-on account. Every destructive action - end a process, stop or restart a service - goes through the app's own themed confirm dialog, never a stock message box.
Performance monitor
Performance Monitor (F11) reads System.Diagnostics.PerformanceCounter directly: % Processor Time for CPU, with a per-core breakdown; Available and Committed bytes for RAM; % Disk Time and read/write bytes per second per physical disk; bytes sent and received per network adapter; and GPU Engine / GPU Adapter Memory summed the same way Task Manager itself does. Disks, adapters and GPUs are enumerated with WMI (Win32_DiskDrive, Win32_VideoController) rather than assumed to be singular. GPU counter instances are rescanned only every 8 ticks instead of every tick, because polling GetInstanceNames() on the GPU categories every second was spamming the Application event log with unrelated Perflib provider-reload errors. Every cell keeps 60 samples of history at a 1s tick, so its graph always shows the last minute.
Storage analyzer
Storage Analyzer (F4, elevated on Ctrl+F4) has two scanners feeding the same treemap. An elevated scan of a local NTFS volume enumerates the Master File Table by file ID and queries sizes directly from the volume, avoiding a path-by-path directory walk. Ordinary accounts, network paths, non-NTFS filesystems, and any MFT failure automatically fall back to the parallel Win32 FindFirstFileEx walker. That fallback drops the unused 8.3-name lookup, carries the \\?\ prefix for long paths, and runs one worker per logical processor. Neither route follows reparse points, and inaccessible entries are counted as skipped rather than making the scan fail.
The map is drawn rather than built. A system drive is easily 200,000 visible rectangles, which is not a number of WPF elements any layout pass survives, so the whole thing is emitted in a single render pass, with the hover and selection rings on a separate overlay layer so moving the pointer never repaints the map underneath. Layout is a squarified treemap, the Bruls, Huizing and van Wijk algorithm, placing children largest first in rows along the shorter side so the rectangles stay close to square and therefore readable and clickable.
The depth and minimum-size controls are view filters applied at draw time, never rescans, which is why they are instant, and neither loses a byte: a folder capped by depth is drawn as one rectangle carrying its whole subtree, and a file hidden by the size floor still counts inside the rectangle of the folder holding it.
Rectangles are colored either by what a file is, from a small table of extension categories painted in the family's neon set with anything unrecognized left gray, so color always means identified, or by which top-level folder of the scan it came from, one hue per branch stepped by the golden angle. Deleting from the map goes through the shell's own Recycle Bin call, so the tab has no permanent-delete path at all, and the map is updated in place afterwards rather than rescanned.
Event Viewer
Event Viewer (Ctrl+F12, always elevated) reads Application, System and Security through System.Diagnostics.Eventing.Reader rather than the older EventLog API, because EventLogQuery can push level filtering into the query itself and read newest-first. Each log is capped at 2,000 records and loaded in batches of 50 on a dedicated long-running thread, since a pooled thread can be reused mid-COM-cleanup and crash. There is no unelevated route to this tab at all: the Security log throws an access-denied error for a standard token, so the tab only exists behind Ctrl+F12.
Registry Editor
Registry Editor (Ctrl+F11, always elevated) is the only one of the four with no background thread anywhere - registry reads are fast enough not to need one. The tree lazy-loads one level at a time with a placeholder-node pattern across all five hives, and Ctrl+F searches key and value names within whatever is already loaded rather than walking the whole registry, which is what real regedit's search does and blocks on. Because the registry has no atomic rename call, renaming a key is implemented as copy-subtree-to-new-name then delete-old. Deleting a key warns, more seriously than the process-kill confirm, that the whole subtree goes with it.
Themes & languages
KillerShell ships with thirteen themes: Dark, Light, Black, 98SE, Blood, Greed, Cyanotic, Ectoplasm, Decay, Malaise, Sepulchre, Delirium and Mourning. Four of them - Dark, Light, Black and 98SE - each carry six accent colors on top of the palette; the other nine are a palette on their own. All of it switches live from the rail, with nothing to restart. The interface is localized in fifteen languages: English, Spanish, German, French, Hungarian, Italian, Turkish, Polish, Czech, Japanese, Chinese in both Traditional and Simplified, Bengali, Russian and Kazakh. Every visible string is resolved through a resource lookup, so switching language reflows the whole window with no restart.
The whole app also scales. Rolling the wheel over the title-bar wordmark drives a layout transform on the content host, roughly 2% a notch between 70% and 250%, remembered between runs. Because it is a layout transform rather than a render transform, text reflows and re-rasterizes at the new size instead of being stretched as a bitmap, and the title bar and footer stay a fixed size so the wordmark never moves out from under the pointer.
Glossary
Plain-language definitions of the terms used on this page, in alphabetical order.
| Term | What it means |
|---|---|
| Archive | A .zip, .tar, .tar.gz, .tgz or .gz file. KillerShell opens these as folders, using the readers that ship inside .NET Framework plus a tar reader of its own, so browsing one costs no extra dependency. |
| Binary file | A file that holds data rather than readable text, such as a program, image, or archive. KillerShell detects these by a null byte near the start and skips them during a content search. |
| Byte-order mark | A few bytes at the very start of a text file that state how its characters are encoded. KillerShell reads it so unusual encodings are handled correctly. |
| Case sensitivity | Whether uppercase and lowercase letters are treated as different. The default is off, so "Error" and "error" both match; a toggle turns it on for both names and content. |
| Content search | Searching inside the text of files for a run of characters, as opposed to searching their names. |
| Include / exclude filter | Semicolon-separated patterns that decide which files are searched. Include narrows the pass to matching files; exclude drops matching files and folders. |
| Font fallback | Drawing a character from a second font when the chosen one has no glyph for it. KillerShell does this per character in the terminal, so a prompt's powerline and git symbols render whatever font you picked. |
| Index | A prebuilt catalog of files that some search tools rely on. KillerShell does not use one, which is why it needs no setup and no waiting for a catalog to build. |
| Line number | The position of a matched line within its file, counted from the top, recorded so you can jump straight to it. |
| OSC 7 | A short escape sequence a shell emits to announce its current directory. KillerShell reads it, which is why the folder shown on a shell tab's toolbar stays right through a cd or a script. |
| Portable | Able to run from anywhere without being installed. KillerShell runs as a loose .exe and shows a PORTABLE badge when it does. |
| Regular expression | A precise pattern-matching notation. KillerShell converts each wildcard filename pattern into one internally to test names quickly. |
| Streaming | Reading a file or a folder tree a small piece at a time instead of loading it whole, which keeps memory low no matter the size. |
| Term | One search request: a pattern plus whether it applies to filenames or file contents. A single pass can run many terms at once. |
| Treemap | A picture of a disk in which every file is a rectangle sized by how much room it takes up and folders are drawn as outlines around their contents, so the things using the space are the things you see first. |
| Wildcard | A filename pattern where an asterisk stands for any characters and a question mark for a single character, such as *.log or report_?.txt. |