Created
February 24, 2025 21:45
-
-
Save ardnew/1af92b9a9e8bff96785870cf8ca9b55a to your computer and use it in GitHub Desktop.
Run as Administrator from WSL
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# | |
# == SUMMARY =================================================================== | |
# | |
# This script is used to invoke a native Windows program (.exe) or batch script | |
# (.bat, .cmd, .com) — referred to as COMMAND below — as an Administrator. | |
# | |
# It is intended to be used from a WSL shell session, where the target program | |
# runs in the parent Windows environment with elevated privileges. | |
# | |
# You can then reuse this shell script as a common mechanism for running | |
# frequently-used tools requiring admin rights. | |
# | |
# You do not need to copy or modify the script for each tool; | |
# just create a specially-nammed symlink pointing to the script. | |
# | |
# The script will resolve the native command based on the name of the symlink | |
# (i.e., argv[0]). Detailed instructions are provided below. | |
# | |
# ---------------------------------------------------------------------------- | |
# | |
# There are two different methods for using this script: | |
# | |
# -- Direct -- | |
# | |
# This is the simpler method. It allows a user to invoke the target COMMAND | |
# directly with command line arguments. It requires two files: | |
# | |
# 1. "/a/b/foo/{.,}COMMAND.{exe,bat,cmd,com}" | |
# The Windows program/script you wish to run as administrator. | |
# This file must be an executable or a symlink to one. | |
# 2. "/a/b/foo/COMMAND{.*sh}" | |
# Symlink pointing to this script. | |
# | |
# !! Files 1 and 2 must be in the same dir. | |
# | |
# -- Indirect -- | |
# | |
# This method allows for some extra processing to occur prior to calling the | |
# target COMMAND. It requires three files: | |
# | |
# 1. "/a/b/foo/{.,}COMMAND.{exe,bat,cmd,com}" | |
# The Windows program/script you wish to run as administrator. | |
# This file must be an executable or a symlink to one. | |
# 2. "/a/b/foo/{.,}COMMAND{.ln,.*sh}" | |
# Symlink pointing to this script. | |
# 3. "/x/y/bar/anything" | |
# Script or program that invokes file 2 above with all necessary args. | |
# | |
# !! Files 1 and 2 must be in the same dir. | |
# | |
# == NOTES ===================================================================== | |
# | |
# The symlink method, of course, only works on systems that support them, which | |
# ironically excludes the native NTFS file system. Thus, this script depends | |
# on some tool for file path translation (from Unix to Windows). | |
# | |
# For WSL, it uses `wslpath`. Prebuilt binaries and build docs on GitHub: | |
# <https://github.com/ardnew/wslpath/> | |
# | |
# On Cygwin, it uses `cygpath`, which comes built-in by default: | |
# <https://www.cygwin.com/>. | |
# | |
# ============================================================================== | |
# define is syntactic sugar for assigning HEREDOC content to a variable. | |
# Works with quoted content (e.g., define foo <<'EOF' ...), as well as scripts | |
# that set -e (since read returns non-zero on success). | |
define() { IFS='\n' read -r -d '' ${1} || true; } | |
# Print an error message to stderr and exit script with the given status. | |
# halt [STATUS [FORMAT [PARAM...]]] | |
halt() { | |
local ret=${1:-127} | |
local pre='error' msg=${2:-'(unspecified)'} | |
[[ $ret =~ ^[0-9]*$ ]] || | |
ret=126 | |
[[ $# -gt 0 && $1 -eq 0 ]] && | |
pre='exit' msg=${2:-'OK'} | |
[[ $# -ge 3 ]] && | |
msg=$( printf -- "${2}" "${@:3}" ) | |
printf -- '%s(%d): %s\n' "${pre}" ${ret} "${msg}" >&2 | |
exit ${ret} | |
} | |
# Prints all arguments after the first and delimited by the first. | |
# Prints only the second argument if exactly two arguments given. | |
# Nothing is printed if fewer than two arguments are given. | |
joinstr() { | |
local d=${1-} f=${2-} | |
shift 2 && printf %s "$f" "${@/#/$d}" | |
} | |
kerver='/proc/version' | |
# Test if a given expression (PCRE) is found in the kernel's version string | |
chkver() { [ "x${1:-}" != x ] && command grep -qiP "${1}" "${kerver}" ; } | |
unset -v cmd | |
cmd=${0##*/} | |
cmd=${cmd%.ln} | |
cmd=${cmd%.*sh} | |
cmd=${cmd%.exe} | |
cmd=${cmd%.bat} | |
ord=( exe bat cmd com ) | |
# Infer the target program/script path from the caller's file path and convert | |
# that to an absolute Windows file path. | |
unset -v target winpath | |
if chkver 'Microsoft'; then | |
winpath=( 'wslpath' -w ) | |
elif chkver 'Cygwin'; then | |
winpath=( 'cygpath' -l -a -w ) | |
else | |
[[ -r "${kerver}" ]] || | |
halt 3 'cannot determine platform: file not found: %s' "${kerver}" | |
kerver=$( cat "${kerver}" ) | |
halt 4 'cannot make Windows path: unknown platform: %s' "${kerver:-'<!NULL>'}" | |
fi | |
pathbin=$( type -P "${winpath[0]:-}" ) || | |
halt 5 'required tool not found: %s' "${winpath[0]:-'<!NULL>'}" | |
winpath[0]=${pathbin} | |
[[ x${TRACE} != x ]] && set -x | |
self=${0%/*} | |
unset -v target targpath | |
for ext in "${ord[@]}"; do | |
while read -re path; do | |
targpath=$( realpath -eq "${path}/${cmd}.${ext}" ) | |
[[ -e "${targpath}" ]] || continue | |
if target=$( "${winpath[@]}" "${targpath}" ); then | |
[[ -x "${targpath}" ]] || chmod +x "${targpath}" || | |
halt 6 'failed to enable execute permissions: %s\n' "${targpath}" | |
break | |
fi | |
# always check CWD "." first, then script dir ${self}, and finally ${PATH} | |
done < <( echo ".:${self}:${PATH}" | tr ':' '\n' ) | |
[[ -z $target ]] || break | |
done | |
[[ -n ${target} ]] || | |
halt 7 'target executable not found: %s extensions: %s)' \ | |
"${target:-'<!NULL>'}" "${ord[*]/#/.}" | |
# Invoke PowerShell with the "Start-Process" cmdlet and the "runas" start verb, | |
# forwarding all arguments received. | |
pshash() { | |
perl -e' | |
use strict; | |
use warnings; | |
# double/single quote without escaping | |
my ($QQ, $Q) = map {chr} 0x22, 0x27; | |
($_, @_) = @ARGV; | |
my $tab = $ENV{indent} || ""; | |
#@_ = map { qq|"$_"| } @_; | |
my @opt = ( | |
FilePath => $_, | |
Verb => q(RunAs), | |
Wait => q($true), | |
# WindowStyle => q(Hidden), | |
# RedirectStandardInput => q("in"), | |
# UseNewEnvironment => q($true), | |
( @_ > 0 ? ( ArgumentList => qq|@_| ) : () ) | |
); | |
while ($_ = shift @opt) { | |
printf "%s%s = %s\n", $tab, $_, | |
# enquote unless it begins with a sigil ($), quote, or at-sym (@). | |
map { /^\s*[\$${Q}@]/ ? $_ : qq|${Q}$_${Q}| } shift @opt | |
} | |
' "${@}" | |
} | |
define star <<_EOF_ | |
{ | |
\$Options = @{ | |
$( indent=$'\t\t' pshash "${target}" "$@" ) | |
} | |
Start-Process @Options | |
Exit \$LASTEXITCODE | |
} | |
_EOF_ | |
exec cat <( powershell.exe -Command "& ${star}" ) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment