Winbang
Winbang adds Unix-like shebang support to Windows, Allowing scripts to run without specifying an interpreter or requiring file extensions. It selects the interpreter from the shebang and can also use file-extension associations when present.
Installation
Cargo
cargo install winbang
Setup
To get the full benefits of Winbang, the following is required:
- Manually associate desired filetypes to
winbang.exe(ideally.sh,.zsh,.py, and other common script extensions.) - Follow Extensionless File Association instructions
- Set up a config file (optional, recommended)
Extensionless File Association
This unlocks the true potential of Winbang. Allowing for a Unix-like file experience making file extensions much less important and taking advantage of standard shebang lines and shebang-like lines.
-
Run in an elevated command prompt:
NOTICE
winbang.exepath may need adjustment based on your installation.
assoc .="No Extension"
ftype "No Extension"=^"^%USERPROFILE%\.cargo\bin\winbang.exe^" "%1"
assoc "No Extension"\DefaultIcon=%SystemRoot%\System32\imageres.dll,-68
DefaultIcon imageres.dll indexes:
15application icon68script icon102file icon
- Restart computer.
Behavior
When invoked from a command prompt, Winbang always executes the script. When
invoked from a GUI context such as explorer.exe, it prompts the user for an
action by default.
Winbang is extensible and supports file association by extension or shebang.
Shebang runtimes can be proxied, for example mapping #!/bin/bash to
C:\msys64\msys2_shell.cmd with appropriate arguments.
If no matching association exists in the configuration, Winbang searches for the
interpreter in PATH.
env shebangs are supported via basic emulation rather than invoking the env
binary. For example, #!/usr/bin/env python3 directly executes python3. The
-S flag is supported, allowing multiple interpreter arguments (e.g.
#!/usr/bin/env -S python3 -u -O). No other env flags are supported.
WARNING
By default, an action prompt is shown when launched from a GUI shell; disabling this behavior could lead to an increased security risk. (The same risk as running any untrusted application/script.)
Shell verb pass-through
When Winbang is associated with an extension, Windows routes every shell verb:
open, edit, print, printto, runas, UIAccess through Winbang's
ProgID. Winbang owns all of these and proxies non-open verbs to whatever
handler the extension would have used before Winbang was selected.
How it works:
- For a non-open verb, Winbang resolves the extension's underlying ProgID via
HKCR\.<ext>\(Default), then executes that ProgID'sshell\<verb>\commandvalue. - If that verb is absent on the underlying ProgID, Winbang falls back to its
openverb. If that is also absent, Winbang exits non-zero.
Tip When associating Winbang with an extension, pick it via Explorer's "Open with -> Always" rather than
assoc .<ext>=Applications\<winbang-exe>. Explorer writes UserChoice, which leaves the original ProgID intact atHKCR\.<ext>\(Default), which is what pass-through reads to find the previous handler.For example:
assoc .py=Applications\<winbang-exe>would overwrite.py's ProgID pointer (Python.File->Winbang), making the original handler's verbs unreachable through.py.
You can read more about Windows Shell verbs at: https://learn.microsoft.com/en-us/windows/win32/shell/fa-verbs
Config File Template
%PROGRAMDATA%/Winbang/config.toml or %APPDATA%/Winbang/config.toml
The configuration files will not merge, only one will be used. If
allow_user_config is set to true in the %PROGRAMDATA% config, then the user
config will be used. If not, the %PROGRAMDATA% config will be used.
config.toml
# allow_user_config = true # Optional, default is false, only valid in %PROGRAMDATA% config.
# List of GUI shells to use when launching files in GUI mode
= ["explorer.exe", "dopus.exe"]
# Default operation if no file association matches
= "prompt" # Optional, default: "prompt", only affects when launched via GUI.
[]
# Viewer used for regular files
= "code"
#args = "$script"
[]
# Viewer used for large files (>= 5MB)
= 5
= "notepad++"
#args = "$script"
# [[file_associations]]
# exec_runtime = "deno" # Required
# view_runtime = "code" # Optional
# shebang_interpreter = "deno" # Optional
# extension = ".ts" # Optional
# exec_argv_override = "run @{script}" # Optional
# verb_edit = "\"C:\\Windows\\Notepad.exe\" \"%1\"" # Optional
# verb_print = "notepad /p \"%1\"" # Optional
# verb_printto = "notepad /pt \"%1\" \"%2\"" # Optional
# verb_runas = "..." # Optional
# verb_uiaccess = "..." # Optional
# File associations
[[]]
= "python"
= "thonny"
= "python"
= "py"
[[]]
= "ruby"
= "code"
= "ruby"
= "rb"
= "prompt"
[[]]
= "C:\\msys64\\msys2_shell.cmd"
= "code"
= "bash"
= "sh"
= "-defterm -here -no-start -ucrt64 -shell bash -c \"$(cygpath -u @{script_unix})\""
[[]]
= "zsh"
= "zsh"
= "sh"
= "execute"
[[]]
= "deno"
= "ts"
= "deno"
= "run -A @{script}"
[[]]
= "node"
= "node"
= "js"
[[]]
= "perl"
= "runemacs"
= "perl"
= "pl"
exec_argv_override will expand the following special variables:
@{script}: The full script file path with double-backslashes (e.g.,C:\\Users\\username\\test.sh).@{script_unix}: The script file path with forward slashes (e.g.,C:/Users/username/test.sh).@{passed_args}: Additional arguments passed from the runtime to the script interpreter.
Example/Test Files
Deno Script
./denotest
#!/usr/bin/env deno
async function main() {
console.log("Hello from Deno!");
const fileName = "test.txt";
try {
await Deno.writeTextFile(fileName, "Hello from Deno!");
console.log(`File ${fileName} created successfully.`);
} catch (error) {
console.error(`Failed to create file ${fileName}:`, error);
}
const promptExit = prompt("Press Enter to exit...");
}
main();
Ruby Script
./rubytest
#!/usr/bin/env ruby
puts
File.open(, ) do
file.write()
end
puts
STDIN.gets