Skip to content

Instantly share code, notes, and snippets.

@ardnew
Created February 24, 2025 21:45
Show Gist options
  • Save ardnew/1af92b9a9e8bff96785870cf8ca9b55a to your computer and use it in GitHub Desktop.
Save ardnew/1af92b9a9e8bff96785870cf8ca9b55a to your computer and use it in GitHub Desktop.
Run as Administrator from WSL
#!/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