docs: String copyFile(
              final ExecutionContext executionContext,
              final File file,
              final InputStream input,
              final String content,
              final INodeEntry node,
              String remotePath
      ) throws
        FileCopierException
      {
          File workingdir = null;
          String scriptargs;
          String dirstring;
  
          //get project or framework property for script-exec args
          final Framework framework = executionContext.getFramework();
          //look for specific property
          scriptargs = framework.getProjectProperty(executionContext.getFrameworkProject(),
                                                    SCRIPT_COPY_DEFAULT_COMMAND_PROPERTY);
  
  
          if (null != node.getAttributes().get(SCRIPT_ATTRIBUTE)) {
              scriptargs = node.getAttributes().get(SCRIPT_ATTRIBUTE);
          }
          if (null == scriptargs) {
              throw new FileCopierException(
                  "[script-copy file copier] no attribute " + SCRIPT_ATTRIBUTE + " was found on node: "
                  + node
                      .getNodename() + ", and no " + SCRIPT_COPY_DEFAULT_COMMAND_PROPERTY
                  + " property was configured for the project or framework.",
                  StepFailureReason.ConfigurationFailure
              );
          }
  
          dirstring = framework.getProjectProperty(executionContext.getFrameworkProject(),
                                                   SCRIPT_COPY_DEFAULT_DIR_PROPERTY);
          if (null != node.getAttributes().get(DIR_ATTRIBUTE)) {
              dirstring = node.getAttributes().get(DIR_ATTRIBUTE);
          }
          if (null != dirstring && !"".equals(dirstring)) {
              workingdir = new File(dirstring);
          }
  
          final File srcFile =
                  null != file ?
                          file :
                          BaseFileCopier.writeTempFile(executionContext, file, input, content);
  
  
          //create context data with node attributes
          Map<String, Map<String, String>> newDataContext;
          final Map<String, Map<String, String>> nodeContext =
                  DataContextUtils.addContext("node", DataContextUtils.nodeData(node), executionContext.getDataContext());
  
          final HashMap<String, String> scptexec = new HashMap<String, String>(){{
              //set filename of source file
              put("filename", srcFile.getName());
              put("destfilename", srcFile.getName());
              put("file", srcFile.getAbsolutePath());
              //add file, dir, destination to the file-copy data
          }};
  	
          if(null != remotePath && !remotePath.endsWith("/")) {
              scptexec.put("destfilename", new File(remotePath).getName());
          }
  
          if (null != workingdir) {
              //set up the data context to include the working dir
              scptexec.put("dir", workingdir.getAbsolutePath());
          }
  
          newDataContext = DataContextUtils.addContext("file-copy", scptexec, nodeContext);
  
          //expand remote filepath if we are copying a script
          String copiedFilepath;
          if (null == remotePath) {
              copiedFilepath = framework.getProjectProperty(executionContext.getFrameworkProject(),
                      SCRIPT_COPY_DEFAULT_REMOTE_FILEPATH_PROPERTY);
              if (null != node.getAttributes().get(REMOTE_FILEPATH_ATTRIBUTE)) {
                  copiedFilepath = node.getAttributes().get(REMOTE_FILEPATH_ATTRIBUTE);
              }
              if (null != copiedFilepath) {
                  if (!(copiedFilepath.contains("${file-copy.filename}")
                          || copiedFilepath.contains("${file-copy.file}"))
                          && !copiedFilepath.endsWith("/")) {
                      copiedFilepath += "/";
                  }
                  copiedFilepath = DataContextUtils.replaceDataReferencesInString(copiedFilepath, newDataContext);
              }
          } else {
              //we are copying to a specific destination
              copiedFilepath = remotePath;
          }
  
          //put file in a directory
          if (null != copiedFilepath && copiedFilepath.endsWith("/")) {
              copiedFilepath += srcFile.getName();
          }
  
          scptexec.put("destination", null != copiedFilepath ? copiedFilepath : "");
  
          newDataContext = DataContextUtils.addContext("file-copy", scptexec, nodeContext);
  
          final Process exec;
  
          String remoteShell = framework.getProjectProperty(executionContext.getFrameworkProject(),
                                                            SCRIPT_COPY_DEFAULT_REMOTE_SHELL);
          if (null != node.getAttributes().get(SHELL_ATTRIBUTE)) {
              remoteShell = node.getAttributes().get(SHELL_ATTRIBUTE);
          }
  
          try {
              if (null != remoteShell) {
                  exec = ScriptUtil.execShellProcess(executionContext.getExecutionListener(), workingdir, scriptargs,
                                                     nodeContext, newDataContext, remoteShell, "script-copy");
              } else {
                  exec = ScriptUtil.execProcess(executionContext.getExecutionListener(), workingdir, scriptargs,
                                                nodeContext,
                                                newDataContext, "script-copy");
              }
          } catch (IOException e) {
              throw new FileCopierException(e.getMessage(), StepFailureReason.IOFailure, e);
          }finally {
              if(null == file) {
                  if (!ScriptfileUtils.releaseTempFile(srcFile)) {
                      executionContext.getExecutionListener().log(
                              Constants.WARN_LEVEL,
                              "Unable to remove local temp file: " + srcFile.getAbsolutePath()
                      );
                  }
              }
          }
  
          final Thread errthread;
          Thread outthread = null;
          final int result;
          final boolean success;
  
  
          final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
          try {
              exec.getOutputStream().close();
              errthread = Streams.copyStreamThread(exec.getErrorStream(), System.err);
              if (null == copiedFilepath) {
                  outthread = Streams.copyStreamThread(exec.getInputStream(), byteArrayOutputStream);
              }
              errthread.start();
              if (null != outthread) {
                  outthread.start();
              }
              result = exec.waitFor();
              System.err.flush();
              byteArrayOutputStream.flush();
              errthread.join();
              if (null != outthread) {
                  outthread.join();
              }
              exec.getErrorStream().close();
              exec.getInputStream().close();
              success = 0 == result;
          } catch (InterruptedException e) {
              Thread.currentThread().interrupt();
              throw new FileCopierException(e.getMessage(), StepFailureReason.Interrupted, e);
          } catch (IOException e) {
              throw new FileCopierException(e.getMessage(), StepFailureReason.IOFailure, e);
          }
          if (!success) {
              if (scriptargs.contains("${file-copy.destination}") && null == copiedFilepath) {
                  executionContext.getExecutionListener().log(0,
                          "[script-copy]: ${file-copy.destination} is referenced in the file-copy script, but its value " +
                                  "could not be determined. " +
                                  "The node " + node.getNodename() + " may need a " + REMOTE_FILEPATH_ATTRIBUTE
                                  + " attribute.");
              }
              throw new FileCopierException("[script-copy]: external script failed with exit code: " + result,
                                            NodeStepFailureReason.NonZeroResultCode);
          }
  
          if (null != copiedFilepath) {
              return copiedFilepath;
          }
          //load string of output from outputstream
          final String output = byteArrayOutputStream.toString();
          if (null == output || output.length() < 1) {
              throw new FileCopierException("[script-copy]: No output from external script",
                                            Reason.ScriptFileCopierPluginExpectedOutputMissing);
          }
          //TODO: require any specific format for the data?
          //look for first line of output
          final String[] split1 = output.split("(\\r?\\n)");
          if (split1.length < 1) {
              throw new FileCopierException("[script-copy]: No output from external script",
                                            Reason.ScriptFileCopierPluginExpectedOutputMissing);
          }
          final String remotefilepath = split1[0];
  
          executionContext.getExecutionListener().log(3, "[script-copy]: result filepath: " + remotefilepath);
  
          return remotefilepath;
      }