| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304 |
- #Requires -Version 5.1
- <#
- .SYNOPSIS
- 将 JAR 包转换为 Windows EXE 可执行文件(基于 Launch4j)
- .DESCRIPTION
- 自动下载 Launch4j(首次运行),生成配置文件并调用 Launch4j 生成 EXE。
- EXE 为启动器,运行时会调用本机已安装的 JRE 执行 JAR。
- .PARAMETER JarPath
- 输入的 JAR 文件路径(必填)
- .PARAMETER OutputExe
- 输出的 EXE 文件路径。默认与 JAR 同目录、同名 .exe
- .PARAMETER MainClass
- 主类全名,例如 com.example.Main。JAR 已含 Main-Class 时可省略
- .PARAMETER AppTitle
- 应用标题,用于错误提示窗口
- .PARAMETER MinJreVersion
- 最低 JRE 版本,默认 1.8.0
- .PARAMETER IconPath
- 可选的应用图标 (.ico)
- .PARAMETER HeaderType
- gui = 无控制台窗口;console = 显示控制台。默认 gui
- .PARAMETER DontWrapJar
- 为 true 时 EXE 与 JAR 分离(JAR 不嵌入 EXE),默认 false(嵌入 JAR)
- .PARAMETER Launch4jDir
- Launch4j 安装目录,默认 tools/jar2exe/launch4j
- .EXAMPLE
- .\jar2exe.ps1 -JarPath "..\..\dist\mesclient.jar" -MainClass "com.mes.ui.MesClient"
- .EXAMPLE
- .\jar2exe.ps1 -JarPath "app.jar" -OutputExe "app.exe" -MainClass "com.example.App" -IconPath "app.ico"
- #>
- [CmdletBinding()]
- param(
- [Parameter(Mandatory = $true)]
- [string]$JarPath,
- [string]$OutputExe = "",
- [string]$MainClass = "",
- [string]$AppTitle = "Java Application",
- [string]$MinJreVersion = "1.8.0",
- [string]$IconPath = "",
- [ValidateSet("gui", "console")]
- [string]$HeaderType = "gui",
- [bool]$DontWrapJar = $false,
- [string]$Launch4jDir = ""
- )
- $ErrorActionPreference = "Stop"
- [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
- function Write-Step([string]$Message) {
- Write-Host "[jar2exe] $Message" -ForegroundColor Cyan
- }
- function Write-Ok([string]$Message) {
- Write-Host "[jar2exe] $Message" -ForegroundColor Green
- }
- function Write-Err([string]$Message) {
- Write-Host "[jar2exe] $Message" -ForegroundColor Red
- }
- function Resolve-FullPath([string]$Path) {
- if ([string]::IsNullOrWhiteSpace($Path)) {
- return ""
- }
- return (Resolve-Path -LiteralPath $Path).Path
- }
- function Ensure-Launch4j([string]$Dir) {
- $launch4jExe = Join-Path $Dir "launch4j.exe"
- if (Test-Path -LiteralPath $launch4jExe) {
- return $launch4jExe
- }
- Write-Step "未检测到 Launch4j,正在下载..."
- $zipUrl = "https://downloads.sourceforge.net/project/launch4j/launch4j-3/3.50/launch4j-3.50-win32.zip"
- $zipPath = Join-Path $env:TEMP "launch4j-3.50-win32.zip"
- try {
- Invoke-WebRequest -Uri $zipUrl -OutFile $zipPath -UseBasicParsing
- }
- catch {
- throw "下载 Launch4j 失败: $($_.Exception.Message)`n请手动下载并解压到: $Dir`n下载地址: $zipUrl"
- }
- if (-not (Test-Path -LiteralPath $Dir)) {
- New-Item -ItemType Directory -Path $Dir -Force | Out-Null
- }
- Expand-Archive -LiteralPath $zipPath -DestinationPath $Dir -Force
- $nestedDir = Join-Path $Dir "launch4j"
- if (Test-Path -LiteralPath (Join-Path $nestedDir "launch4j.exe")) {
- Get-ChildItem -LiteralPath $nestedDir | ForEach-Object {
- $target = Join-Path $Dir $_.Name
- if (Test-Path -LiteralPath $target) {
- Remove-Item -LiteralPath $target -Recurse -Force
- }
- Move-Item -LiteralPath $_.FullName -Destination $target -Force
- }
- Remove-Item -LiteralPath $nestedDir -Recurse -Force -ErrorAction SilentlyContinue
- }
- if (-not (Test-Path -LiteralPath $launch4jExe)) {
- throw "Launch4j 解压后未找到 launch4j.exe,请检查目录: $Dir"
- }
- Write-Ok "Launch4j 已就绪: $launch4jExe"
- return $launch4jExe
- }
- function Get-JarMainClass([string]$JarFile) {
- $jarTool = Join-Path $env:JAVA_HOME "bin\jar.exe"
- if (-not (Test-Path -LiteralPath $jarTool)) {
- $jarTool = "jar"
- }
- $manifest = & $jarTool xf $JarFile META-INF/MANIFEST.MF 2>$null
- $manifestPath = Join-Path (Get-Location) "META-INF\MANIFEST.MF"
- if (-not (Test-Path -LiteralPath $manifestPath)) {
- return ""
- }
- $content = Get-Content -LiteralPath $manifestPath -Encoding UTF8
- Remove-Item -LiteralPath "META-INF" -Recurse -Force -ErrorAction SilentlyContinue
- foreach ($line in $content) {
- if ($line -match '^Main-Class:\s*(.+)$') {
- return $Matches[1].Trim()
- }
- }
- return ""
- }
- function New-Launch4jConfig {
- param(
- [string]$ConfigPath,
- [string]$JarFile,
- [string]$ExeFile,
- [string]$MainClassName,
- [string]$Title,
- [string]$JreMin,
- [string]$Icon,
- [string]$Header,
- [bool]$SeparateJar
- )
- $jarAbs = $JarFile.Replace("\", "/")
- $exeAbs = $ExeFile.Replace("\", "/")
- $iconXml = ""
- if (-not [string]::IsNullOrWhiteSpace($Icon)) {
- $iconAbs = $Icon.Replace("\", "/")
- $iconXml = " <icon>$iconAbs</icon>"
- }
- $xml = @"
- <?xml version="1.0" encoding="UTF-8"?>
- <launch4jConfig>
- <dontWrapJar>$($SeparateJar.ToString().ToLower())</dontWrapJar>
- <headerType>$Header</headerType>
- <jar>$jarAbs</jar>
- <outfile>$exeAbs</outfile>
- <errTitle>$Title</errTitle>
- <cmdLine></cmdLine>
- <chdir>.</chdir>
- <priority>normal</priority>
- <downloadUrl>https://www.java.com/download/</downloadUrl>
- <supportUrl></supportUrl>
- <stayAlive>false</stayAlive>
- <restartOnCrash>false</restartOnCrash>
- <manifest></manifest>
- $iconXml
- <jre>
- <path></path>
- <bundledJre64Bit>false</bundledJre64Bit>
- <bundledJreAsFallback>false</bundledJreAsFallback>
- <minVersion>$JreMin</minVersion>
- <maxVersion></maxVersion>
- <jdkPreference>preferJre</jdkPreference>
- <runtimeBits>64/32</runtimeBits>
- </jre>
- </launch4jConfig>
- "@
- $utf8NoBom = New-Object System.Text.UTF8Encoding $false
- [System.IO.File]::WriteAllText($ConfigPath, $xml, $utf8NoBom)
- }
- # ---------- 主流程 ----------
- $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
- if ([string]::IsNullOrWhiteSpace($Launch4jDir)) {
- $Launch4jDir = Join-Path $scriptDir "launch4j"
- }
- if (-not (Test-Path -LiteralPath $JarPath)) {
- Write-Err "找不到 JAR 文件: $JarPath"
- exit 1
- }
- $jarFull = Resolve-FullPath $JarPath
- if ([string]::IsNullOrWhiteSpace($OutputExe)) {
- $OutputExe = [System.IO.Path]::ChangeExtension($jarFull, ".exe")
- }
- else {
- $parent = Split-Path -Parent $OutputExe
- if (-not [string]::IsNullOrWhiteSpace($parent) -and -not (Test-Path -LiteralPath $parent)) {
- New-Item -ItemType Directory -Path $parent -Force | Out-Null
- }
- if (Test-Path -LiteralPath $OutputExe) {
- $OutputExe = (Resolve-Path -LiteralPath $OutputExe).Path
- }
- else {
- $OutputExe = [System.IO.Path]::GetFullPath($OutputExe)
- }
- }
- if ([string]::IsNullOrWhiteSpace($MainClass)) {
- Push-Location (Split-Path -Parent $jarFull)
- try {
- $MainClass = Get-JarMainClass -JarFile $jarFull
- }
- finally {
- Pop-Location
- }
- if ([string]::IsNullOrWhiteSpace($MainClass)) {
- Write-Err "无法从 JAR 读取 Main-Class,请通过 -MainClass 参数指定主类"
- exit 1
- }
- Write-Step "从 JAR 读取主类: $MainClass"
- }
- if (-not [string]::IsNullOrWhiteSpace($IconPath)) {
- if (-not (Test-Path -LiteralPath $IconPath)) {
- Write-Err "找不到图标文件: $IconPath"
- exit 1
- }
- $IconPath = Resolve-FullPath $IconPath
- }
- $exeDir = Split-Path -Parent $OutputExe
- if (-not [string]::IsNullOrWhiteSpace($exeDir) -and -not (Test-Path -LiteralPath $exeDir)) {
- New-Item -ItemType Directory -Path $exeDir -Force | Out-Null
- }
- Write-Step "JAR: $jarFull"
- Write-Step "EXE: $OutputExe"
- Write-Step "主类: $MainClass"
- $launch4jExe = Ensure-Launch4j -Dir $Launch4jDir
- $configPath = Join-Path $exeDir "launch4j-config.xml"
- New-Launch4jConfig -ConfigPath $configPath `
- -JarFile $jarFull `
- -ExeFile $OutputExe `
- -MainClassName $MainClass `
- -Title $AppTitle `
- -JreMin $MinJreVersion `
- -Icon $IconPath `
- -Header $HeaderType `
- -SeparateJar $DontWrapJar
- Write-Step "正在生成 EXE..."
- $process = Start-Process -FilePath $launch4jExe -ArgumentList "`"$configPath`"" -Wait -PassThru -NoNewWindow
- if ($process.ExitCode -ne 0) {
- Write-Err "Launch4j 执行失败,退出码: $($process.ExitCode)"
- Write-Err "配置文件: $configPath"
- exit $process.ExitCode
- }
- if (-not (Test-Path -LiteralPath $OutputExe)) {
- Write-Err "EXE 未生成,请检查 Launch4j 日志"
- exit 1
- }
- Write-Ok "转换完成!"
- Write-Ok "EXE 文件: $OutputExe"
- Write-Host ""
- Write-Host "说明:" -ForegroundColor Yellow
- Write-Host " - EXE requires JRE $MinJreVersion or higher on target machine"
- Write-Host " - If using -DontWrapJar, distribute JAR and EXE together"
- Write-Host " - For bundled JRE, configure bundled JRE path in Launch4j and regenerate"
- exit 0
|