badass-runtime-plugin
badass-runtime-plugin copied to clipboard
Sign exe before MSI installer is created
The task jpackageImage
creates an application image.
The task jpackage
creates an MSI Installer of the application image (at least on Windows).
Requirement: I need to sign the executable which is wrapped in the MSI installer.
I can sign the MSI installer after the task jpackage
is finished. However, virus scanner seem to need a signed executable in some cases as well. Hence I tried to first run jpackageImage
, afterwards I signed the executable and after that I called jpackage
to pack the result.
Unfortunately jpackage
depends on jpackageImage
and creates again a new application image which is not signed. How can I achieve that the already existing application image is used instead of creating a new one.
Thanks for any hint!
I sign it within the jpackageImageTask (kotlin dsl):
/**
* Extends the jpackageImage task.
* For Windows, signs the exe file
*/
tasks.jpackageImage {
when (osdetector.os) {
Os.WINDOWS.osName -> {
doLast {
logger.info("Windows jpackageImage")
//Add signing code here
}
}
else -> {
doLast {
logger.info("Other OS")
}
}
}
}
Note, you can ignore the osdetector
stuff, I have that in because I also build for macOS.
Hopefully, that helps
I'm adding this as enhancement because, although really it was just a question, similar plugins do just have a simple way to get things signed as they are built, and, in some cases, jpackage itself can be instructed to do signing.
Things to think about:
- Assuming this will be implemented using
signtool
:- Which parameters are necessary?
- Which optional parameters are worth adding?
- How should the overall API look?
(Try to fit with the way it will work for macOS - on macOS,
jpackage
just has additional flags. A future jpackage may gain the same for Windows for parity.)
Thanks @vewert for your input 👍 It helped me to find a solution that worked for me.
I have an external bat script that I would like to start. Unfortunately with your solution I did not manage to start the script within tasks.jpackageImage
(Note: I am a noob in Gradle).
The solution I found is very similar. For the record I post it below...
def os = org.gradle.internal.os.OperatingSystem.current()
task runExecutableSign(type:Exec) {
doFirst {
println "Start Executable signing process ..."
workingDir = file('./_sign/')
commandLine = ['cmd', '/C', 'start', 'jsign-exe.bat']
// cmd /C start D:/XXX/_sign/jsign-exe.bat
}
}
if (os.windows) {
jpackageImage.finalizedBy runExecutableSign
}
The jsign-exe.bat
is very simple. The only issue I encountered was that the exe file was set to read-only. Hence you need to remove this flag and than sign it with your tools. Something like this
:: remove read-only flag
attrib -r "...foo.exe"
:: sign EXE
"%PROGRAMFILES(X86)%\Windows Kits\10\App Certification Kit\signtool.exe" sign /tr http://timestamp.digicert.com /td sha256 /fd sha256 /a "...foo.exe"
Hope this might be useful for others.
- How should the overall API look? (Try to fit with the way it will work for macOS - on macOS,
jpackage
just has additional flags. A future jpackage may gain the same for Windows for parity.)
@hakanai see above for options I need to sign..
jpackage builds both an MSI and an EXE for Windows.
To sign the EXE I have this small task:
ext {
signTool = "C:\\Program Files (x86)\\Windows Kits\\10\\App Certification Kit\\signtool.exe"
}
task signWindowsInstaller(type: Exec) {
executable = "${signTool}"
args = [
'sign', '/v',
'/d', project.name,
'/f', "${signCertificate}",
'/p', "${signPassword}",
'/fd', 'SHA256',
'/t', 'http://timestamp.digicert.com',
"${buildDir}\\native\\${project.name}-${project.version}.exe",
"${buildDir}\\native\\${project.name}-${project.version}.msi"
]
}
In order to sign the EXE within the MSI, I have overridden the package resource Post-image script, project-name-post-image.wsf. This is a custom script that is executed after the application image is created and before the MSI installer is built for both .msi and .exe packages.
https://docs.oracle.com/en/java/javase/16/jpackage/override-jpackage-resources.html#GUID-405708DC-0243-49FC-84D9-B2A7F0A011A9
<?xml version="1.0" ?>
<job>
<script language="VBScript">
Const WshRunning = 0
Const WshFinished = 1
Const WshFailed = 2
Set WshShell = CreateObject("WScript.Shell")
Dim AttribOffExec : Set AttribOffExec = WshShell.Exec("attrib -r @projectName@\@[email protected]")
While AttribOffExec.Status = WshRunning
WScript.Sleep 50
Wend
signCommand = """@signTool@"" sign /v /a /d ""@projectName@"" /f ""@signCertificate@"" /p @signPassword@ /fd SHA256 /t http://timestamp.digicert.com @projectName@\@[email protected]"
Dim SignExec : Set SignExec = WshShell.Exec(signCommand)
While SignExec.Status = WshRunning
WScript.Sleep 50
Wend
Dim AttribOnExec : Set AttribOnExec = WshShell.Exec("attrib +r @projectName@\@[email protected]")
Dim output
If SignExec.Status = WshFailed Then
output = SignExec.StdErr.ReadAll
Else
output = SignExec.StdOut.ReadAll
End If
Dim StdOut : Set StdOut = CreateObject("Scripting.FileSystemObject").GetStandardStream(1)
Stdout.Write output
</script>
</job>
I used to use signtool, but now I use the jsign plugin: https://ebourg.github.io/jsign/ It can be called from within the jpackageImage task.
I also noticed about the read-only problem (sorry I should have mentioned that problem).
Here is my more complete code, including jsign and removing read-only:
/**
* Extends the jpackageImage task.
* For Windows, signs the exe file
*/
tasks.jpackageImage {
when (osdetector.os) {
Os.WINDOWS.osName -> {
doLast {
logger.info("Windows jpackageImage")
Paths.get(jpackageWinDir.absolutePath, "${project.name}.exe").toFile().setWritable(true)
val jsign = project.extensions.getByName("jsign") as groovy.lang.Closure<*>
jsign(
"file" to Paths.get(jpackageWinDir.absolutePath, "${project.name}.exe").toString(),
"name" to longName,
"url" to projectUrl,
"keystore" to pfxFile.absolutePath,
"storepass" to pfxPass,
"alg" to signingAlg,
"tsaurl" to tsaUrl,
"tsmode" to tsMode
)
}
}
else -> {
doLast {
logger.info("Other OS")
}
}
}
}