diff --git a/core/pom.xml b/core/pom.xml index 85b7c2ac8d35dc2b781a9d762d19572239fa52b5..750f23ed4a2a1257e0658ace73231b5cc9511dc7 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -737,7 +737,7 @@ THE SOFTWARE. com.sun.winsw winsw - 1.8 + 1.9 bin exe provided diff --git a/core/src/main/java/hudson/lifecycle/WindowsInstallerLink.java b/core/src/main/java/hudson/lifecycle/WindowsInstallerLink.java index b6f041025a93c78c6de7385364011edb7ecebba1..0639405f54fc0c96ea0df5bc87b63ca70ba0a00b 100644 --- a/core/src/main/java/hudson/lifecycle/WindowsInstallerLink.java +++ b/core/src/main/java/hudson/lifecycle/WindowsInstallerLink.java @@ -30,7 +30,6 @@ import hudson.AbortException; import hudson.Extension; import hudson.util.StreamTaskListener; import hudson.util.jna.DotNet; -import hudson.Launcher.LocalLauncher; import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; @@ -127,9 +126,8 @@ public class WindowsInstallerLink extends ManagementLink { ByteArrayOutputStream baos = new ByteArrayOutputStream(); StreamTaskListener task = new StreamTaskListener(baos); task.getLogger().println("Installing a service"); - int r = new LocalLauncher(task).launch() - .cmds(new File(dir, "hudson.exe").getPath(), "install") - .stdout(task.getLogger()).pwd(dir).join(); + int r = WindowsSlaveInstaller.runElevated( + new File(dir, "hudson.exe"), "install", task, dir); if(r!=0) { sendError(baos.toString(),req,rsp); return; @@ -197,8 +195,8 @@ public class WindowsInstallerLink extends ManagementLink { } LOGGER.info("Starting a Windows service"); StreamTaskListener task = StreamTaskListener.fromStdout(); - int r = new LocalLauncher(task).launch().cmds(new File(installationDir, "hudson.exe"), "start") - .stdout(task).pwd(installationDir).join(); + int r = WindowsSlaveInstaller.runElevated( + new File(installationDir, "hudson.exe"), "start", task, installationDir); task.getLogger().println(r==0?"Successfully started":"start service failed. Exit code="+r); } catch (IOException e) { e.printStackTrace(); diff --git a/core/src/main/java/hudson/lifecycle/WindowsSlaveInstaller.java b/core/src/main/java/hudson/lifecycle/WindowsSlaveInstaller.java index 325fde4d5af70f45b99d24ae3ab5290ac04d9ab2..29f6f5f86292aec430488504c543ba1db8d2ccd5 100644 --- a/core/src/main/java/hudson/lifecycle/WindowsSlaveInstaller.java +++ b/core/src/main/java/hudson/lifecycle/WindowsSlaveInstaller.java @@ -23,30 +23,37 @@ */ package hudson.lifecycle; +import com.sun.jna.Native; import hudson.Launcher.LocalLauncher; import hudson.Util; +import hudson.model.TaskListener; import hudson.remoting.Callable; import hudson.remoting.Engine; import hudson.remoting.jnlp.MainDialog; import hudson.remoting.jnlp.MainMenu; import hudson.util.StreamTaskListener; import hudson.util.jna.DotNet; +import hudson.util.jna.Kernel32Utils; +import hudson.util.jna.SHELLEXECUTEINFO; +import hudson.util.jna.Shell32; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.io.output.ByteArrayOutputStream; import javax.swing.*; -import static javax.swing.JOptionPane.ERROR_MESSAGE; -import static javax.swing.JOptionPane.OK_CANCEL_OPTION; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.net.URL; +import static hudson.util.jna.SHELLEXECUTEINFO.*; +import static javax.swing.JOptionPane.*; + /** * @author Kohsuke Kawaguchi */ @@ -89,6 +96,44 @@ public class WindowsSlaveInstaller implements Callable, A return null; } + /** + * Invokes slave.exe with a SCM management command. + * + *

+ * If it fails in a way that indicates the presence of UAC, retry in an UAC compatible manner. + */ + static int runElevated(File slaveExe, String command, TaskListener out, File pwd) throws IOException, InterruptedException { + try { + return new LocalLauncher(out).launch().cmds(slaveExe, command).stdout(out).pwd(pwd).join(); + } catch (IOException e) { + if (e.getMessage().contains("CreateProcess") && e.getMessage().contains("=740")) { + // fall through + } else { + throw e; + } + } + + // error code 740 is ERROR_ELEVATION_REQUIRED, indicating that + // we run in UAC-enabled Windows and we need to run this in an elevated privilege + SHELLEXECUTEINFO sei = new SHELLEXECUTEINFO(); + sei.fMask = SEE_MASK_NOCLOSEPROCESS; + sei.lpVerb = "runas"; + sei.lpFile = slaveExe.getAbsolutePath(); + sei.lpParameters = "/redirect redirect.log "+command; + sei.lpDirectory = pwd.getAbsolutePath(); + sei.nShow = SW_HIDE; + if (!Shell32.INSTANCE.ShellExecuteEx(sei)) + throw new IOException("Failed to shellExecute: "+ Native.getLastError()); + + try { + return Kernel32Utils.waitForExitProcess(sei.hProcess); + } finally { + FileInputStream fin = new FileInputStream(new File(pwd,"redirect.log")); + IOUtils.copy(fin,out.getLogger()); + fin.close(); + } + } + /** * Called when the install menu is selected */ @@ -131,7 +176,7 @@ public class WindowsSlaveInstaller implements Callable, A // install as a service ByteArrayOutputStream baos = new ByteArrayOutputStream(); StreamTaskListener task = new StreamTaskListener(baos); - r = new LocalLauncher(task).launch().cmds(slaveExe, "install").stdout(task).pwd(dir).join(); + r = runElevated(slaveExe,"install",task,dir); if(r!=0) { JOptionPane.showMessageDialog( dialog,baos.toString(),"Error", ERROR_MESSAGE); @@ -148,7 +193,7 @@ public class WindowsSlaveInstaller implements Callable, A public void run() { try { StreamTaskListener task = StreamTaskListener.fromStdout(); - int r = new LocalLauncher(task).launch().cmds(slaveExe, "start").stdout(task).pwd(dir).join(); + int r = runElevated(slaveExe,"start",task,dir); task.getLogger().println(r==0?"Successfully started":"start service failed. Exit code="+r); } catch (IOException e) { e.printStackTrace(); diff --git a/core/src/main/java/hudson/util/jna/Kernel32Utils.java b/core/src/main/java/hudson/util/jna/Kernel32Utils.java new file mode 100644 index 0000000000000000000000000000000000000000..f54b4d9b22610ebca08e875ae35cb12cddf15adb --- /dev/null +++ b/core/src/main/java/hudson/util/jna/Kernel32Utils.java @@ -0,0 +1,53 @@ +/* + * The MIT License + * + * Copyright (c) 2010, CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package hudson.util.jna; + +import com.sun.jna.Pointer; +import com.sun.jna.ptr.IntByReference; + +/** + * + * @author Kohsuke Kawaguchi + */ +public class Kernel32Utils { + /** + * Given the process handle, waits for its completion and returns the exit code. + */ + public static int waitForExitProcess(Pointer hProcess) throws InterruptedException { + while (true) { + if (Thread.interrupted()) + throw new InterruptedException(); + + Kernel32.INSTANCE.WaitForSingleObject(hProcess,1000); + IntByReference exitCode = new IntByReference(); + exitCode.setValue(-1); + Kernel32.INSTANCE.GetExitCodeProcess(hProcess,exitCode); + + int v = exitCode.getValue(); + if (v !=Kernel32.STILL_ACTIVE) { + return v; + } + } + } +}