Introduction
     Please Note: OddJob
        requires StringTheory
       
      
      OddJob provides a set of classes and templates for running and managing
      processes from within your application (executables, batch files etc.).
      
      Oddjob will easily help you:
      
        - Run a process (like batch files and external exes) asynchronously
          from within your app and monitor the output (think of FTPing files via
          an external FTP client)?
- Handle scripting languages (like PHP scripts) synchronously or
          asynchronously?
- Run a user interface to control your service (and bypass UAC)? 
      OddJob also allows you to pass data to started processes and retrieve data
      from them. You use it to send and receive data via the StdIn and StdOut of
      any started process, and OddJob works excellently with NetTalk to
      communicate with processes via TCP/IP.
      
      OddJob also provides the ability to pass data to a started process (via
      the Standard Input as well as on the command line), which allows command
      line applications to be run and data passed to them, as well as retrieving
      the result back from the process. This opens up a huge number of command
      line tools to your application, from simple command line clients, to
      command line server, compilers and even batch files.
      
      We strongly recommend starting with the example applications, which are
      described below in the Example section. 
 
    Jobs and Features
     Not only can you run, monitor, terminate and manage
      any processes that you start, you can also group them into Jobs. 
      
      A Job allows groups of processes to be managed as a unit. Job objects are
      nameable, securable, sharable objects that control attributes of the
      processes associated with them. 
      
      Operations performed on the job object affect all processes associated
      with the job object. If you have used applications such as Google Chrome
      or Internet Explorer then you will have experienced Jobs in action, as
      each tab creates a new process that is a member of the same Job. 
      
      This allows tasks normally performed by threads to be performed by
      processes, providing improved stability and isolation.
      
    
    Using CapeSoft OddJob
    
      Add OddJob to your application in a few
        Easy Steps!
      
        - If this is a Multi-App system, then add the OddJob global extension
          to the data (root) DLL. On the Multi-DLL tab in this app tick on both
          options (This is part of a Multi-DLL program and Export OddJob classes
          from this DLL.)
 If this app is not part of a Multi-DLL system then skip this step
- In the app where you want to create and use an OddJob object, add
          the global extension to the app. If this is part of a Multi-DLL
          solution then go tothe Multi-DLL tab and only tick the first option on
          (This is part of a Multi-DLL program)
- Add the Local extension to the procedure that you wish to use OddJob
          in.
 This adds a JobObject object to the procedure.
 
 
- Add code to initialize and kill the object
 
 Initializing the object passing a Job name is option. You can call Init without specifying a Job name, in which
          case the object will be initialized, but no Job will be created. This
          is useful when you want to use the functionality that doesn't require
          a JobObject (for example starting a process, enumerating processed on
          the machine, or killing a process).
 
 
            - Initialize the object (for example before the window opens in ThisWindow.Init(), or at the start of the
              procedure).
 
 Job.Init('MyJob')
 
 
- Kill the object and clean up:
 
 Job.Kill()
 
 Note: You can also call Job.Kill(false)
              to clean up all memory and handles, but leave any processes in the
              job running. By default calling Kill will
              end all processes started, but by passing False
                to the method all processes will be left running and the
              Windows JobObject will remain until all processes have closed.
 
Using the JobObject
      The code below demonstrates three different ways of creating and
      interacting with new processes
      
      
Starting Processes
      
      
        
output          StringTheory
  code
    
    Job.CreateProcess('', 'notepad.exe', '', '')
        Job.CreateProcess('takeaction.BAT', jo:SW_HIDE, false, , , , , output)
        if not Job.CreateProcess('grep.com ?',jo:SW_HIDE , , , , , , output) 
        Message('CreateProcess failed: ' & output.GetVal())
    else
        if not output.SaveFile('output.txt')
            Message('Failed to save the output. Error ' & |
                     output.winErrorCode & ': ' & |
                     output.FormatMessage(output.winErrorCode))
        else
            Job.ExecuteFile('output.txt')
        end
    end
       
      Kill all processes associated with a Job
      
      Job.CloseAll()
      
      Get information about the a Job
      
      Job.GetInformation(jo:JobObjectBasicAccountingInformation)
      
      
      How to call a command line application and get the output
      This example demonstrates the best practice for calling applications on
      the command line. It retrieves the output for StdOut if any is written,
      but works equally well for applications that don't actually write anything
      to StdOut. It has the added benefit of essentially synchronizing the
      process class, as it reads from the output on a timer until the handle is
      close and the process is complete. In almost every case, when you call a
      command line application and need to retrieve output, a synchronous
      behaviour is desirable. Not that if you call this on a thread that display
      a user interface, the interface will be unresponsive while the StdOut pipe
      is being read from, as the read is handled on a separate Accept loop.
      
      
        exitVal     long
st          StringTheory
commandLine string(512)
jo          Class(JobObject)   ! added by the template
            end
   CODE
			
    commandLine = 'Dcdirdmp.exe -if dicomdir'      st.Free() 
    jo.CreateProcess(commandLine, jo:SW_HIDE, 1, , , , exitVal, st)
       
     
    Examples
     Demo This is the main
      OddJob example application. 
      
      It enables you to run a batch file or exe and retrieve the output data. It
      also creates a job and allows you to add as many instances of Notepad to
      the job as desired, as well as listing information about the job and
      processes running on the machine. Any application can be added to a Job,
      regardless of whether it is an application that you have created, or an
      application such as Notepad from a third party.
      
      Html Editor
      
      This provides a simple HTML editor and demonstrates the use of NetTalk for
      inter process communication. NetTalk is required to compile this example.
      The HTML editor is a completely standalone application that is integrated
      with the example using a Job to control the process and NetTalk.
      
      Note: The feHtmlEditor.app file does not
      need to be compiled, the shipped executable will be used by default, and
      it provides a standalone HTML editor that is fully integrated with the
      example application. In order to compile the actual editor executable
      NetTalk and FileExplorer are required. 
    The OddJob Templates
    
      
The Global Extension template:
      There are no options for the Global extension.
      The OddJob local extension template
      Add this extension to a window procedure where you want to control any
      started processes. This window must exist as long as the processes are
      running, or at least as long as you are interacting with them in any way.
      
      The local extension allows you to specify the type of object being used.
      By far the most common is the standard Job object, which provides the
      foundation for OddJob's functionality. 
    Version Historywc saf    
     Download latest version 
here
      
      Version 1.47 - 2 Oct 2025 
      
        - Update: OddJob.Inc was using StringTheoryDLLMode and
          StringTheoryLinkMode. Changed to OddJobDM and OddJobLM.
Version 1.46 - 27 May 2025 
      
      Version 1.45 - 24 May 2021 
      
      Version 1.44 - 28 September 2020
        - Fix: Function being called as Procedure
Version 1.43 - 13 September 2018
        - Add: Clarion 11 to install.
Version 1.42 - 24 July 2018
        - Change: Conditional Compile, _ODDJOB_=>1,
          is added to the project if OddJob is included in the app (and not
          disabled.) This allows code dependent on OddJob to know that OddJob is
          available.
Version 1.41 - 5 September 2017
        - DebugOutput method renamed to Trace.
- Calls to ErrorTrap always sent to Trace.
Version 1.40 - 28 September 2016
        - BindToCPU method extended so
          that the program can be bound to any subset of CPUs, a specific CPU,
          or a random CPU. (Thanks to Alison Peters.)
Version 1.39 - 28 April 2015
        - Removed CREATE_BREAKAWAY_FROM_JOB and
          added CREATE_PRESERVE_CODE_AUTHZ_LEVEL to
          CreateProcess flags. (Thanks to Carlos Gutiérrez and Bijan
          Hosseinian.)
Version 1.38 - 3 March 2015
        - Added procq.hProcess = self.Processes.hProcess
          after deep assign. (Thanks to Rick Martin.)
Version 1.37 - 25 February 2015
        - Installer supports Clarion 10.
- Removed unnecessary use of %cwversion
Version 1.36 - 7 March 2014
        - Build for use with StringTheory 2.00 and later.
Version 1.34 - 3 February 2014
        - Update: Installer detects Clarion 9.1
Version 1.33 - 14 May 2013
        - Update: Installer detects Clarion 9.
Version 1.32 - 14 March 2013
        - Changed to Ver4 object/template management system. IMPORTANT
           READ
              THIS.
- Add: support for Multi-Proj in C8
Version 1.31 - 28 November 2011 Requires
        StringTheory 1.52 or later
        - Added: New classes and example
          application for named pipe handling. Simplifies the creation of named
          pipes and transfer between a server and a client for both Clarion
          applications and application written in other languages which use
          pipes. 
- PipeCore class: Provides the core reading
          and writing to and from pipes. Typically not used directly - the
          PipeServer and PipeClient inherit from this class.
          
            - Properties:
              
            
- Methods:
              
                - Read Procedure (long hPipe,
                  *string binData, long binLen, *long bytesRead), long, proc,
                  virtual
- Write Procedure (long hPipe,
                  *string binData, long binLen, *long bytesWritten), long, proc,
                  virtual
- PeekPipe Procedure (long hPipe,
                  *string binData, long binLen, *long bytesRead, <*long
                  bytesAvailable>, <*long bytesMessage>), long, virtual
 
 
 
- PipeServer Class: A Multi-threaded pipe
          server class. Allows the handling of any number of pipe clients (for
          each client an OS thread is created and destroyed when no longer
          needed).
          
            - Properties:
              
                - _running bool ! Set while the server is active.
- _threadID ulong ! ID of the listen thread
- _hpipe long ! Handle to the current pipe
 
- Methods:
              
                - Run Procedure (), virtual ! Start
                  the listening thread to wait for new client connections
- Stop Procedure (), virtual
- CreatePipe Procedure (<string
                  pipeName>), long, proc, virtual
- ConnectPipe Procedure (long
                  hPipe), long, virtual
- DisconnectPipe Procedure (long
                  hPipe), long, virtual
- TakeRequest Procedure (*string
                  request, long requestBytes, *string reply, *long replyBytes),
                  virtual
 
 
- PipeClient class:
          
            - Properties
              
                - hPipe long ! Handle to the current pipe
 
- Methods:
              
                - OpenPipe Procedure (<string
                  pipeName>, long timeOut = 0), long, virtual
- ClosePipe Procedure (), virtual
- Write Procedure (*string
                  writeData, *long dataLen), long, proc, virtual
- Read Procedure (*string readData,
                  *long dataLen), long, proc, virtual
- SetPipeState Procedure (long
                  dwMode = jo:PIPE_READMODE_MESSAGE), long, proc, virtual
- WaitForPipe Procedure (long
                  timeOut), long, virtual
 
 
 
- Added: a Pipes
          example which has a pipe server and pipe client app demonstrating the
          usage of the new Pipe classes.
Version 1.30 - 09 September 2011
        - Changed: Moved all equates and types to
          the OddJobEq.inc file
- Added: JobObject.BindToCPU
          - Binds the process to a single CPU (can be called at any point after
          process creation and applies to all threads and child processes)
- Added: ProcessAffinity example.
          Demonstrates the new process affinity methods such as BindToCPU,
          SetProcessAffinity, GetProcessAffinity
          etc.
- Added: JobObject.GetSystemInformation()
          method to retrieve system information
- Added: joSYSTEM_INFO type for retrieving
          system information
- Added: JobObject.CountProcessors()
          method to return the number of processors for the current system
- Added: JobObject.SetProcessAffinity()
          method to allow the process to be limited to a specific subset of the
          processors available on the system
- Added: JobObject.GetProcessAffinity()
          method to allow the current processor mask to be retrieved along with
          the system mask (which show which processors are available and can be
          used in setting the process affinity mask).
Version 1.29 - 29 June 2011
        - New: Added the following methods:
          
            - HandleFromPID Procedure(ulong PID),
              unsigned, virtual
 Returns the Process Handle when passed the Process ID (PID)
- GetProcessPath
              Procedure(*joProcessQType processQ), string, virtual
 Returns the full path for the EXE identified by the current
              Process record in the passed queue. Note that the path returned
              uses the Windows NT Device name format, not the DOS style drive
              paths. Use the GetDrives method to map
              between device names and paths.
- DeviceNameForDrive Procedure(string
              sDrive), string, virtual
 Returns the full device name when passed the drive identifier (for
              example 'C:'  or 'D:')
- GetDrives Procedure(*joDrivesQType
              drivesQ), bool, proc, virtual
 Populates a queue with a list of all drives and their full NT
              Device Names.
 
- New: Extended the example application to
          demonstrate the new methods - populate a list of all drives and their
          NT device names; get the full path of a process based on the process
          ID of the selected item in the processes list etc.
Version 1.28 - 9 April 2011
        - New: Added a KillProcess
          method. This kills one or more instances of a process when passed the
          process name (or optionally a substring of the name).
- New: KillProcess
          example application. Demonstrates creating a small command line
          application using a hand coded project. This application kills all
          matching processes when passed a process name and can also optionally
          kill just the first instance found, or all processes where the name
          matches a substring passed.
 
Version 1.27 - 13 December 2010
        - New: Replaced the RunAsUser
          method. This method can be used from a service to run an application
          as the currently logged in user and bypasses UAC on system that have
          it enabled.
 Example:
 Job.RunAsUser('C:\Windows\Notepad.exe', '')
 
- Note: RunAsUser requires the ability to
          duplicate the token used by the WinLogon process associated with the
          current user. This is generally only available to services running in
          the LocalSystem account.
- Fix: The GetUserToken, GetSessions
          and related methods have been modified to allow zero session IDs
          (which are valid).
- Improvement: The Init
          method can be called multiple times without causing any undesirable
          behaviour.
- Improvement: The object is initialized on
          creation, and the calling Init explicitly is only required to create
          an actual Job (all non Job functionality can be used without calling
          Init manually).
- Improvement: Kill
          is called on destruction to clean up, and no longer needs to be called
          manually.
- Improvement: Calling Kill
          multiple times no longer results in undesirable behaviour.
- Improvement: Calling LoadLibs
          multiple times now only loads libraries that have no already been
          loaded and will not cause any undesirable behaviour.
- Fix: The Userenv.dll library was not being
          loaded correctly and the handle ended up pointing at the wrong
          library.
- Fix: The CreateEnvironmentBlock
          and DestroyEnvironmentBlock functions
          causing a GPF as a result of an incorrect library handle.
- New: The ErrorTrap
          method now allows the method name to be passed as an optional
          parameter
- New: LogonUser
          method returns a token for the specific user using the passed user
          name and password to authenticate.
- New: CloseHandle
          method. Closes the passed handle if it is valid.
- New: DuplicateToken
          method duplicates the passed token, and can convert handles to
          impersonation tokens into a primary token handles.
- New: RunAsUser
          example
Version 1.26 - 16 September 2010
        - Fix: joOpenProcess() was calling the wrong API as the result of a
          typographical error.
Version 1.26 - 16 September 2010
        - Updated the installer to ensure that the RED file is correctly
          updated to include .c and .h files for the MD5 functionality.
- StringTheory is now provided as separate install, so please ensure
          that you download the current StringTheory install when updating
          OddJob.
- Fixed a potential GPF in the CreateProcess method where a pointer
          was assigned without the &= operator.
- Template fix for Clarion 7 (was no sheet and tab on extension).
- Template tweak - assert for installing StringTheory.
- Template 01.tpw - includes version 1.66 of the 01.tpw
Version 1.25 - 15 June 2010
        - StringTheory Class
          
            - Fixed Between() method including the left hand delimiter in the
              returned string.
- Additional handling of boundary cases in the Replace() method.
 
Version 1.24 - 01 June 2010
        - StringTheory Class
          
            - Fixed a bug in the Replace method, when the string being
              replaced is the end of the text being searched.
 
Version 1.23 - 01 June 2010
        - StringTheory Class
          
            - Added bounds checking and corrected unhandled boundary cases for
              the following methods:
              
                - Replace (handles replacement from
                  the start and end of the string, where the resultant string is
                  empty, where the resultant string is a single character etc.)
- PathOnly (handles the case where
                  the path contains only a forward or back slash)
- Split (handles if a "line" is
                  blank)
- ToBlob (handles the string being
                  assigned to the blob being empty, or being a single character)
 
- Verified that Between and Before are both safe.
 
Version 1.22 - 26 May 2010
        - Added optional clip parameter to StringTheory Append
          method.
- Updated docs for the StringTheory SetValue
          method
Version 1.21 - 17 May 2010
        - Added handling to SetValue for passing
          zero length strings (the StringTheory value is disposed of and the
          Length methods will return zero).
- Added GetSessions method that fills a
          queue with all sessions.
- Added FindActiveSession method.
- Added an optional accessToken process
          to the RunAsUser method to allow the method
          to run a process as any user, not just the current one.
- Added an optional dontStore parameter
          to the RunAsUser method, which allows the
          process information to not be stored. This is set to True (1) by
          default to maintain the same behaviour as previous version that did
          not support this functionality.
- RunAsUser now creates the processes,
          associates it with the current job (if the breakAwayFromJob
            parameter is not set to True, and the 
            .dontAssociateProcesses property is not set to True), and
          stores the created process information in to .processes queue (if the
          dontStore parameter is not set to False (0)).
- Added a GetToken method to retrieve the
          Access Token associated with the passed session.
- Added a RunAsUser example that demonstrates running processes with a
          variety of credentials.
- Added APIs for token manipulation (runtime loaded):
          
            - joAccessCheck(), joAdjustTokenGroups(),
              joAdjustTokenPrivileges(), joGetTokenInformation(),
              joSetThreadToken(), joSetTokenInformation()
 
Version 1.20 - 17 May 2010
        - Fixed a potential memory leak when Calling CreateProcess and reading
          data from a process.
- Reduced memory usage when calling a processes that the StdOut will
          be read from.
- Fixed the length of the string created when using Base64 encoding to
          ensure that the encoded string is always padded onto a 4 byte
          boundary.
- Added the base64NoWrap property, which when
          set will not add line wrapping to the Base64 encoded string.
- Fixed errors in the Base64 encoding.
- Improved efficiency when assigning new values to the string that
          have the same length as the string stored
- Add a new csBlowfish class to provide support for the Blowfish
          cipher.
- Added Blowfish encryption and decryption support to StringTheory,
          via the following methods:
          
            - InitBlowfish
- KillBlowfish
-  Encrypt
- Decrypt
-  EncryptString
- DecryptString
-  SetKey
 
- Note: Future releases will support CBC (Cipher Block Chaining) to
          allow data of arbitrary length to be encrypted. The current version
          requires that that the data encrypted has a length that is a multiple
          of 8 (in bytes). 
- Documented base properties and methods available to all classes,
          including:
- Base Methods
          
            
              
                | Debug | Conditionally output to the Windows debug output depending
                  on whether the .debug property is set to true or not. |  
                | DebugOutput | Unconditionally output a string to the Windows debug output. |  
                | ErrorTrap | Callback method used to trap errors. |  
                | FormatMessage | Converts an API error code into the associated error
                  message. |  
                | ToCstring | Creates a new cstring from the passed string. |  
                | FreeCstring | Safely frees a cstring reference variable. |  
                | Ulong64ToReal | Converts an unsigned 64 bit integer to a Real |  
                | GetReg | Returns the value of the specified registry key. |  
                | PutReg | Sets the value of the specified registry key. |  
 Base Properties
            
              
                | displayErrors | Sets whether or not the class displays errors using the
                  Message function. |  
                | logErrors | Sets whether or not errors are sent to the Windows Debug
                  Output |  
                | errorCode | The last error code |  
                | errorMessage | The last error message |  
                | debug | Determines whether or not debug output is produced |  
                | debugPrefix | The prefix used for debug output |  
 
- New GetRegand PutReg
          methods (see above).
- Documented new methods and properties.
- New main example app demonstrates a far wider variety of ways in
          which to start and interact with processes.
Version 1.10 - 06 April 2010
        - New StringTheory methods:
          
            - Before - returns the portion of the
              string that occurs before the specified string seperator.
- After - returns the portion of the
              string that occurs after the specified string seperator.
- Between - returns the portion of the
              string that occurs between the two specified string seperators.
- Base64Encode/Base64Decode
                - Base64 encode and decode either a passed string, or the
              value stored by the StringTheory object.
- MD5 - Create an MD5 hash of the
              string.
- New string delimiter processing methods:
              
                - Split - splits the string into
                  sub strings, which are stored in the .lines property.
                  Optionally allows the "line" start and end delimeters to be
                  specified.
- Join - joins the all the lines
                  (after the string has been slit using Split, and optionally
                  wraps each line in the specified delimiters).
 
- Slice - returns a string containing
              the data between the specified start and end characters (a
              "slice").
 
            - Crop - Crops the stored value using
              the pass start and end parameters.
- Sub - returns a sub-string of the
              specified length, starting at the specified character.
 
- Fixed the ClipLen method infinitely loop
          as it was calling itself rather than calling ClipLength().
- Fixed the Base64Encode method adding
          extra spaces in the encoded string.
- Added: FromBlob method, stores the
          contents of the passed BLOB in the StringTheory object.
- Added ToBlob method, saves the contents
          of the string into the passed BLOB.
Version 1.03 - 20 October 2009
        - Added: Additional optional parameter to the CreateProcess methods to
          allow the initial window mode to be specified. This allows the process
          to be started as shown, hidden, minimised, maximised etc. without
          having to call the more advanced CreateProcess method that supported
          this.
Version 1.02 - 20 October 2009
        - Fixed: CreateProcess causing a GPF when used to start a process that
          was hidden and did not require any IO (no StringTheory object was
          passed).
- Added: Additional logging to CreateProcess for simpler debugging.
- Fixed: CreateProcess could potentially call the AddProcess method
          twice, resulting in it failing the second time. This would not affect
          the application's behaviour, but would display an erroneous error in
          the debug log if logging was enabled and being captured.
Version 1.01 - 12 October 2009
        - StringTheory Class
          
            - Fixed: the PathOnly method, which was
              returning a blank string.
- Optimised: the Replace method in the
              case where both strings are a single character
- Fixed: Replace to allow an empty
              replacement string to be specified (which removes the searched for
              string)
- Fixed: FileNameFromPath method.
- Renamed: the ProcessQType to joProcessQType to ensure that it
              does not conflict with other tools using the same type.
- Added: GetProcessMemoryInfo method to
              get the memory usage information for any process on the system.
- Fixed: Missing Editor folder in the Demo example, causing the
              HTML editor to not function correctly.
 
Version 1.00 - 11 August 2009