From Newsgroup: alt.comp.os.windows-11
Working linux/macOS apkanalyzer example port to Windows for WhatsApp update debugging
So that everyone can benefit, just moments ago I ported the macOS/Linux Android SDK apkanalyzer bash script to a Windows batch implementation.
Newsgroups: comp.mobile.android
Subject: A quick look at the WhatsApp Meta Services Framework server-side 7 day update
Date: Sat, 17 Oct 2026 12:43:55 -0400
Message-ID: <11b08kc$20fv$
1@nnrp.usenet.blueworldhosting.com>
Included are the before:after macOS/Linux:Windows scripts for your use.
BACKGROUND:
A week ago WhatsApp triggered a 7-day warning to update where I decided to debug (in gory detail) the update-process mechanism on the Android ng.
I haven't seen the phone in a week (as the sd card is being replaced),
but it's displayed on my monitor two feet tall, so I had full access to it.
Note that my 2021 Android Galaxy A32-5G USB port is broken so everything
is done over Wi-Fi where I wrote scripts to get around the security restrictions Google forced into Android 11+ to require hands-on access.
adbconnect.bat (previously posted)
adbconnecttls.bat (previously posted)
Since I could connect to Android over Wi-Fi without dealing with Google's security random-port-assignment friction, I got lazy and used the phone
all week without even knowing where it is (it's probably in a drawer).
The battery is 5Ah (which is impressive for a 2021 phone) so it basically
lasts forever before I have to put it back on a 2-hour fast charger.
Each day, for the past week, when I used WhatsApp on the mirror image
on my desktop to watch videos of the great grandkids, WhatsApp would
warn me in a daily countdown that I had 7 days, 6 days, etc. to update.
What was surprising (to me) was the WhatsApp APK came from the Google Play Store repository (via Aurora) and yet the intent to update from WhatsApp
shows the APK actually coming from a /1 folder on WhatsApp servers instead.
curl.exe -L "
https://www.whatsapp.com/android/1/WhatsApp.apk"
Interestingly, when the expiry triggered today, the intent *changed*, which debugging showed is now coming from a /current folder on WhatsApp servers.
curl.exe -L "
https://www.whatsapp.com/current/WhatsApp.apk"
By pulling the existing WhatsApp base.apk to the Windows desktop, we were
able to confirm the /1 & /current folder APKs were identical, and we also confirmed exactly what version changes occurred compared to the base.apk.
adb shell pm path com.whatsapp
adb pull "/data/app/~~Q0AH2roDwIp1Sab6n2Zk7w==/com.whatsapp-SuTfaV-YTnPJBe_GGlwL0w==/base.apk"
First we confirmed the two cryptographic signatures were identical between
the Google Play Store repository version & the WhatsApp website version.
apksigner.bat verify --print-certs {base,current}.apk
Two distinct signing blocks appear because modern Android apps use
multiple signing schemes (in this case, v2/v3/v4 & v1/JAR signing).
For Android 13+
DN: CN=WhatsApp LLC, O=WhatsApp LLC, L=Menlo Park, ST=California, C=US
SHA-256 Digest: fb920d381bee1b2093f27dc8f13d994da629dc91887d0529b35c9a2dc4f4a6c2
For older Android versions
DN: CN=Brian Acton, OU=Engineering, O=WhatsApp Inc., L=Santa Clara, ST=California, C=US
SHA-256 Digest: 3987d043d10aefaf5a8710b3671418fe57e0e19b653c9df82558feb5ffce5d44
From that we concluded that we could safely swap out the current WhatsApp
apk over the existing base WhatsApp apk, without losing data, using adb.
adb install -r WhatsApp.apk
But first we wanted to see what the version and SDK changes would be.
aapt2.exe dump badging {base,current}.apk | findstr /C:"package:"
Which told us how the version code & the version name would increment.
To further debug, we wanted to use apkanalyzer, but my apkanalyzer came
from the legacy Android SDK Tools package, whose launcher was a Unix shell script. In 2017, which is when my SDK tools were installed, Google did not provide a Windows .bat launcher alongside that particular copy and I didn't know yet, that was the reason there was only a Linux/macOS script.
<
https://developer.android.com/tools/apkanalyzer>
It was basically a packaging oversight on the Google side for SDK releases.
type C:\app\editor\android\sdk\tools\source.properties
Pkg.UserSrc=false
Pkg.Revision=26.1.1
Platform.MinPlatformToolsRev=20
Pkg.Dependencies=emulator
Pkg.Path=tools
Pkg.Desc=Android SDK Tools
Since everyone reading this is ware that sometimes you only learn the right way of doing something by doing it the hard way by mistake, what happened
next is I converted that 2017 developer history into the modern era of
OpenJDK 17 & forced that Linux/macOS script to work on Windows.
Linux/macOS:Windows before:after scripts below are therefore instructive.
The main hurdle arose when attempting to run that ported apkanalyzer tool
on Windows to inspect the APK metrics. Because I was using a legacy 2017
SDK script running on modern OpenJDK 17, it broke at first, due to missing Java XML/JAXB modules. This required manually hunting down, downloading,
and mapping a nested chain of dependency libraries (jaxb-runtime, jaxb-api, istack-commons-runtime, and jakarta.activation-api) directly into the
custom batch file's classpath.
However, after a bit of debugging, the Linux/macOS-to-Windows environment successfully stabilized such that the APK analysis completed seamlessly.
Although a final storage constraint on the target device (brought on by a missing SD card) paused the ultimate installation step, the core
engineering successfully built a robust Windows command-line apkanalyzer toolkit to analyze and troubleshoot over Wi-Fi or USB from the desktop.
apkanalyzer.bat apk file-size base.apk
adb shell df -h /data
To that end, below are two files, the first of which is the working Google macOS/Linux apkanalyzer ported to Windows & the second is the original.
gvim C:\app\editor\android\sdk\tools\bin\apkanalyzer.bat
===== < below is the working Windows apkanalyzer.bat script > =====
:: --------------------------------------------------------------------
:: apkanalyzer.bat
:: Conversion of Android sdk Linux/macOS apkanalyzer script to Windows
:: --------------------------------------------------------------------
:: v2p0 20261022 Made APP_HOME portable using dynamic-script-path resolution
:: v1p9 20261022 Shortened CLASSPATH using wildcard and multiline execution
:: v1p8 20261022 Split CLASSPATH to overcome 1K-characters/line limits
:: v1p7 20261022 Added and tested example help command syntax above
:: v1p6 20261022 Added and tested example command syntax above
:: v1p5 20261022 Added jakarta.activation-api.jar to Windows and classpath
:: v1p4 20261022 Added istack-commons-runtime.jar to Windows and classpath
:: v1p3 20261022 Added jaxb-api-2.3.1.jar to Windows and classpath
:: v1p2 20261022 Added jaxb-runtime-2.3.5.jar to Windows and classpath
:: v1p1 20261022 Added variables to set custom paths to dependencies
:: v1p0 20261022 Convert C:\app\editor\android\sdk\tools\bin\apkanalyzer
:: --------------------------------------------------------------------
:: Tested on the base apk installed 20261022 using the commands below:
:: C:\> curl.exe -L "
https://www.whatsapp.com/android/current/WhatsApp.apk"
:: C:\> adb -r WhatsApp.apk
:: C:\> adb shell pm path com.whatsapp
:: C:\> adb pull "... that path ... app-SuTfaV-YTnPJBe_GGlwL0w==/base.apk"
:: --------------------------------------------------------------------
:: To use:
:: C:\> apkanalyzer.bat manifest application-id base.apk
:: C:\> apkanalyzer.bat manifest version-code base.apk
:: C:\> apkanalyzer.bat manifest version-name base.apk
:: C:\> apkanalyzer.bat manifest min-sdk base.apk
:: C:\> apkanalyzer.bat manifest target-sdk base.apk
:: C:\> apkanalyzer.bat manifest permissions base.apk
:: C:\> apkanalyzer.bat manifest debuggable base.apk
:: C:\> apkanalyzer.bat apk summary base.apk
:: C:\> apkanalyzer.bat apk file-size base.apk
:: C:\> apkanalyzer.bat apk download-size base.apk
:: C:\> apkanalyzer.bat files list base.apk
:: C:\> apkanalyzer.bat manifest print base.apk
:: C:\> apkanalyzer.bat dex list base.apk
:: C:\> apkanalyzer.bat dex references base.apk
:: C:\> apkanalyzer.bat apk features base.apk
:: C:\> apkanalyzer.bat files cat --file "AndroidManifest.xml" base.apk
:: C:\> apkanalyzer.bat dex packages base.apk
:: C:\> apkanalyzer.bat resources packages base.apk
:: C:\> apkanalyzer.bat resources names --config default --type string base.apk
:: C:\> apkanalyzer.bat files list base.apk | findstr /i "\.xml"
:: C:\> apkanalyzer.bat resources xml --file "res/xml/splits0.xml" base.apk
:: C:\> apkanalyzer.bat --help
:: C:\> apkanalyzer.bat apk --help
:: C:\> apkanalyzer.bat dex --help
:: C:\> apkanalyzer.bat resources --help
:: --------------------------------------------------------------------
@ECHO OFF
setlocal
REM Dynamically resolve Android SDK Tools Root Path from script location (\tools\bin\ -> \tools\)
for %%I in ("%~dp0..") do set "APP_HOME=%%~fI"
REM Custom OpenJDK 17 Path (Adjust if your Java path changes)
set "JAVA_EXE=C:\Program Files\Microsoft\jdk-17.0.18.8-hotspot\bin\java.exe"
REM All JARs in the SDK lib directory via wildcard
set "CLASSPATH=%APP_HOME%\lib\*"
"%JAVA_EXE%" "-Dcom.android.sdklib.toolsdir=%APP_HOME%" ^
-classpath "%CLASSPATH%" ^
com.android.tools.apk.analyzer.ApkAnalyzerCli %*
endlocal
:: end of apkanalyzer.bat
===== < above is the working Windows apkanalyzer.bat script > =====
gvim C:\app\editor\android\sdk\tools\bin\apkanalyzer
===== < below is the original Linux/macOS apkanalyzer.bat script > =====
#!/usr/bin/env sh
##############################################################################
##
## apkanalyzer start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/.." >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="apkanalyzer"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and APKANALYZER_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Dcom.android.sdklib.toolsdir=$APP_HOME"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/lib/dvlib-26.0.0-dev.jar:$APP_HOME/lib/util-2.2.1.jar:$APP_HOME/lib/jimfs-1.1.jar:$APP_HOME/lib/annotations-13.0.jar:$APP_HOME/lib/ddmlib-26.0.0-dev.jar: \
$APP_HOME/lib/repository-26.0.0-dev.jar:$APP_HOME/lib/sdk-common-26.0.0-dev.jar:$APP_HOME/lib/kotlin-stdlib-1.1.3-2.jar:$APP_HOME/lib/protobuf-java-3.0.0.jar: \
$APP_HOME/lib/apkanalyzer-cli.jar:$APP_HOME/lib/gson-2.3.jar:$APP_HOME/lib/httpcore-4.2.5.jar:$APP_HOME/lib/dexlib2-2.2.1.jar:$APP_HOME/lib/commons-compress-1.12.jar: \
$APP_HOME/lib/generator.jar:$APP_HOME/lib/error_prone_annotations-2.0.18.jar:$APP_HOME/lib/commons-codec-1.6.jar:$APP_HOME/lib/kxml2-2.3.0.jar:$APP_HOME/lib/httpmime-4.1.jar: \
$APP_HOME/lib/annotations-12.0.jar:$APP_HOME/lib/bcpkix-jdk15on-1.56.jar:$APP_HOME/lib/jsr305-3.0.0.jar:$APP_HOME/lib/explainer.jar:$APP_HOME/lib/builder-model-3.0.0-dev.jar: \
$APP_HOME/lib/baksmali-2.2.1.jar:$APP_HOME/lib/j2objc-annotations-1.1.jar:$APP_HOME/lib/layoutlib-api-26.0.0-dev.jar:$APP_HOME/lib/jcommander-1.64.jar: \
$APP_HOME/lib/commons-logging-1.1.1.jar:$APP_HOME/lib/annotations-26.0.0-dev.jar:$APP_HOME/lib/builder-test-api-3.0.0-dev.jar:$APP_HOME/lib/animal-sniffer-annotations-1.14.jar: \
$APP_HOME/lib/bcprov-jdk15on-1.56.jar:$APP_HOME/lib/httpclient-4.2.6.jar:$APP_HOME/lib/common-26.0.0-dev.jar:$APP_HOME/lib/jopt-simple-4.9.jar:$APP_HOME/lib/sdklib-26.0.0-dev.jar: \
$APP_HOME/lib/apkanalyzer.jar:$APP_HOME/lib/shared.jar:$APP_HOME/lib/binary-resources.jar:$APP_HOME/lib/guava-22.0.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $APKANALYZER_OPTS -classpath "\"$CLASSPATH\"" com.android.tools.apk.analyzer.ApkAnalyzerCli "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"
===== < above is the original Linux/macOS apkanalyzer.bat script > =====
NOTE: The single-line CLASSPATH in the macOS/Linux script was broken
artificially into multiple lines so that nntp servers don't barf.
While I would never have done this had I known two hours ago what I now know, this still serves as a rare example of porting Linux/macOS
legacy scripts to work in a modern JAVA-based Windows environment.
As always, please add value where you feel the team would benefit
from your knowledge, suggestions, fixes, corrections and insight.
--
Usenet allows purposefully helpful people to pool their experiences.
--- Synchronet 3.21d-Linux NewsLink 1.2