PS 5.1 parses a BOM-less .ps1 as the system ANSI codepage, garbling the Chinese / em-dash string literals in five scripts. verify-doc-facts.ps1 failed four checks under 5.1 (CI only ran it under pwsh). Add a BOM to the five files and a CI guard that fails any non-ASCII .ps1 lacking one.
212 lines
6.7 KiB
PowerShell
212 lines
6.7 KiB
PowerShell
#requires -Version 5
|
|
|
|
[CmdletBinding()]
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$ProjectDir,
|
|
|
|
[string]$OutDir,
|
|
|
|
[string]$BaseName,
|
|
|
|
[string]$KeystorePath,
|
|
|
|
[string]$KeyAlias = 'androiddebugkey',
|
|
|
|
[string]$StorePass = 'android',
|
|
|
|
[string]$KeyPass = 'android',
|
|
|
|
[string]$DeviceSerial,
|
|
|
|
[switch]$Install,
|
|
|
|
[switch]$Reinstall,
|
|
|
|
[switch]$Clean
|
|
)
|
|
|
|
Set-StrictMode -Version Latest
|
|
$ErrorActionPreference = 'Stop'
|
|
[Console]::InputEncoding = [System.Text.UTF8Encoding]::new($false)
|
|
[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)
|
|
$OutputEncoding = [System.Text.UTF8Encoding]::new($false)
|
|
|
|
if ([string]::IsNullOrWhiteSpace($KeystorePath)) {
|
|
$KeystorePath = Join-Path (Join-Path $env:USERPROFILE '.android') 'debug.keystore'
|
|
}
|
|
|
|
function Get-ToolPath {
|
|
param([Parameter(Mandatory = $true)][string]$Name)
|
|
|
|
$cmd = Get-Command $Name -ErrorAction SilentlyContinue
|
|
if ($cmd) {
|
|
return $cmd.Source
|
|
}
|
|
|
|
$sdkRoot = Join-Path $env:LOCALAPPDATA 'Android\Sdk'
|
|
$buildTools = @()
|
|
if (Test-Path -LiteralPath (Join-Path $sdkRoot 'build-tools')) {
|
|
$buildTools = Get-ChildItem -LiteralPath (Join-Path $sdkRoot 'build-tools') -Directory -ErrorAction SilentlyContinue |
|
|
Sort-Object -Property Name -Descending
|
|
}
|
|
|
|
$fallbacks = @{
|
|
'apktool' = @(
|
|
(Join-Path $env:USERPROFILE 'Tools\apktool\apktool.bat')
|
|
)
|
|
'zipalign' = @($buildTools | ForEach-Object { Join-Path $_.FullName 'zipalign.exe' })
|
|
'apksigner' = @($buildTools | ForEach-Object { Join-Path $_.FullName 'apksigner.bat' })
|
|
'keytool' = @()
|
|
'adb' = @(
|
|
(Join-Path $sdkRoot 'platform-tools\adb.exe'),
|
|
(Join-Path $env:USERPROFILE 'AppData\Local\Android\Sdk\platform-tools\adb.exe')
|
|
)
|
|
}
|
|
|
|
if ($fallbacks.Contains($Name)) {
|
|
foreach ($candidate in $fallbacks[$Name]) {
|
|
if (-not [string]::IsNullOrWhiteSpace($candidate) -and (Test-Path -LiteralPath $candidate)) {
|
|
return $candidate
|
|
}
|
|
}
|
|
}
|
|
|
|
# Attempt auto-bootstrap for supported tools
|
|
$bootstrapScript = Join-Path $PSScriptRoot '..\..\scripts\bootstrap-reverse.ps1'
|
|
$bootstrapSupported = @('adb', 'apktool')
|
|
if ($Name -in $bootstrapSupported -and (Test-Path -LiteralPath $bootstrapScript)) {
|
|
Write-Host "INFO: $Name not found, attempting auto-bootstrap..." -ForegroundColor Yellow
|
|
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $bootstrapScript -Capability @($Name) -SkipRefresh
|
|
$cmd = Get-Command $Name -ErrorAction SilentlyContinue
|
|
if ($cmd) {
|
|
Write-Host "INFO: $Name bootstrapped successfully." -ForegroundColor Green
|
|
return $cmd.Source
|
|
}
|
|
# Re-check fallbacks after bootstrap
|
|
if ($fallbacks.Contains($Name)) {
|
|
foreach ($candidate in $fallbacks[$Name]) {
|
|
if (-not [string]::IsNullOrWhiteSpace($candidate) -and (Test-Path -LiteralPath $candidate)) {
|
|
Write-Host "INFO: $Name found at fallback path after bootstrap." -ForegroundColor Green
|
|
return $candidate
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# Clear error message for tools that cannot be auto-bootstrapped
|
|
$manualHint = switch ($Name) {
|
|
'zipalign' { 'Install Android Build-Tools via Android SDK Manager (sdkmanager "build-tools;35.0.0")' }
|
|
'apksigner' { 'Install Android Build-Tools via Android SDK Manager (sdkmanager "build-tools;35.0.0")' }
|
|
default { "Install $Name manually" }
|
|
}
|
|
throw "Missing required CLI tool: $Name — $manualHint"
|
|
}
|
|
|
|
function Ensure-DebugKeystore {
|
|
param(
|
|
[Parameter(Mandatory = $true)][string]$Path,
|
|
[Parameter(Mandatory = $true)][string]$Keytool,
|
|
[Parameter(Mandatory = $true)][string]$Alias,
|
|
[Parameter(Mandatory = $true)][string]$StorePassword,
|
|
[Parameter(Mandatory = $true)][string]$KeyPassword
|
|
)
|
|
|
|
if (Test-Path -LiteralPath $Path) {
|
|
return
|
|
}
|
|
|
|
$parent = Split-Path -Path $Path -Parent
|
|
if (-not (Test-Path -LiteralPath $parent)) {
|
|
New-Item -ItemType Directory -Path $parent -Force | Out-Null
|
|
}
|
|
|
|
& $Keytool -genkeypair -v -keystore $Path -storepass $StorePassword -keypass $KeyPassword -alias $Alias -keyalg RSA -keysize 2048 -validity 10000 -dname 'CN=Android Debug,O=ReverseSkill,C=CN'
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw 'Failed to generate debug keystore.'
|
|
}
|
|
}
|
|
|
|
if (-not (Test-Path -LiteralPath $ProjectDir)) {
|
|
throw "Project directory not found: $ProjectDir"
|
|
}
|
|
|
|
if ([string]::IsNullOrWhiteSpace($OutDir)) {
|
|
$projectParent = Split-Path -Path $ProjectDir -Parent
|
|
if ([string]::IsNullOrWhiteSpace($projectParent)) {
|
|
$projectParent = [System.IO.Directory]::GetCurrentDirectory()
|
|
}
|
|
$OutDir = $projectParent
|
|
}
|
|
|
|
$apktool = Get-ToolPath -Name 'apktool'
|
|
$zipalign = Get-ToolPath -Name 'zipalign'
|
|
$apksigner = Get-ToolPath -Name 'apksigner'
|
|
$keytool = Get-ToolPath -Name 'keytool'
|
|
$adb = Get-ToolPath -Name 'adb'
|
|
|
|
Ensure-DebugKeystore -Path $KeystorePath -Keytool $keytool -Alias $KeyAlias -StorePassword $StorePass -KeyPassword $KeyPass
|
|
|
|
if (-not (Test-Path -LiteralPath $OutDir)) {
|
|
New-Item -ItemType Directory -Path $OutDir -Force | Out-Null
|
|
}
|
|
|
|
$name = if ($BaseName) { $BaseName } else { Split-Path -Path $ProjectDir -Leaf }
|
|
$unsignedApk = Join-Path $OutDir ($name + '-unsigned.apk')
|
|
$alignedApk = Join-Path $OutDir ($name + '-aligned.apk')
|
|
$signedApk = Join-Path $OutDir ($name + '-signed.apk')
|
|
|
|
if ($Clean) {
|
|
foreach ($path in @($unsignedApk, $alignedApk, $signedApk)) {
|
|
if (Test-Path -LiteralPath $path) {
|
|
Remove-Item -LiteralPath $path -Force
|
|
}
|
|
}
|
|
}
|
|
|
|
& $apktool b $ProjectDir -o $unsignedApk
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw 'apktool build failed.'
|
|
}
|
|
|
|
& $zipalign -f -p 4 $unsignedApk $alignedApk
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw 'zipalign failed.'
|
|
}
|
|
|
|
Copy-Item -LiteralPath $alignedApk -Destination $signedApk -Force
|
|
& $apksigner sign --ks $KeystorePath --ks-key-alias $KeyAlias --ks-pass "pass:$StorePass" --key-pass "pass:$KeyPass" --out $signedApk $alignedApk
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw 'apksigner sign failed.'
|
|
}
|
|
|
|
& $apksigner verify --print-certs $signedApk
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw 'apksigner verify failed.'
|
|
}
|
|
|
|
"unsigned_apk=$unsignedApk"
|
|
"aligned_apk=$alignedApk"
|
|
"signed_apk=$signedApk"
|
|
"keystore=$KeystorePath"
|
|
|
|
if ($Install) {
|
|
$installArgs = @()
|
|
if ($DeviceSerial) {
|
|
$installArgs += '-s'
|
|
$installArgs += $DeviceSerial
|
|
}
|
|
$installArgs += 'install'
|
|
if ($Reinstall) {
|
|
$installArgs += '-r'
|
|
}
|
|
$installArgs += $signedApk
|
|
|
|
& $adb @installArgs
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw 'adb install failed.'
|
|
}
|
|
|
|
"install_device=$DeviceSerial"
|
|
}
|