Skip to content

App Launcher: Universal RDS Remote Application

App Launcher starts a Windows application as an RDS RemoteApp and signs in to it by typing credentials supplied by the 12Port Access Broker. The user never sees or handles the password: the broker injects it into the session, the session is recorded, and user input stays blocked until the sign-in sequence finishes.

A single executable, app-launcher.exe, serves any number of applications. Each application is described by its own section in app-launcher.ini, and each asset selects a section by name. Adding a new application means adding an INI section; nothing needs to be recompiled.

App Launcher drives applications with keystrokes and window detection. It works with any application that accepts keyboard input: desktop clients, terminal emulators such as IBM i Access Client Solutions 5250, and web login pages in Microsoft Edge.


How it works

  1. A user opens an RDS Remote Application asset in 12Port.
  2. The Access Broker starts app-launcher.exe on the RDS server and passes the asset fields listed in Remote Arguments. The launcher requests them through the session's control channel; they are not written to disk.
  3. The launcher reads the Script field and loads the INI section of that name.
  4. It starts the application from the section's exe and args.
  5. It runs the section's script: waits for windows, types field values, presses keys.
  6. It unblocks user input. The user now works in the signed-in application.
  7. Optionally, it waits for the application to close and then cleans up: it ends leftover processes and logs off the RDS session.

Requirements

  • Windows Server with the Remote Desktop Session Host role and a RemoteApp collection.
  • The target application installed on the RDS server.
  • An RDS Host Server asset in 12Port for that server.
  • For web portals: Microsoft Edge.

Installation

1. Get the files

1. Download Required Files
* Executable:
https://bin.12port.com/product/remote-apps/app-launcher/app-launcher.exe
* Configuration File (.ini):
https://bin.12port.com/product/remote-apps/app-launcher/app-launcher.ini

Source file:
https://bin.12port.com/product/remote-apps/ztna.au3
Note: The ztna.au3 source file contains shared code used by all RDS Remote Application scripts. This file is required for compiling modified or custom created RDS app scripts. Place it in the parent folder of the script source, for example app-launcher.au3; the source includes it as ..\ztna.au3.

* Place both the Executable (.exe) and Configuration (.ini) files in the same directory on the RDS Server where the Remote Application will be published. For example: C:\12Port-RDSApps\app-launcher.

2. Place the files

Copy both files into the same folder on every RDS server in the collection, for example:

C:\12Port-RDSApps\app-launcher\app-launcher.exe
C:\12Port-RDSApps\app-launcher\app-launcher.ini

The launcher always reads the INI from its own folder.

Protect the folder. The INI decides which programs run in user sessions. Grant write access to administrators only; users need read and execute access.

3. Publish the RemoteApp

In an elevated PowerShell session on the RD Connection Broker, replacing QuickSessionCollection with your RDS collection name:

New-RDRemoteApp -CollectionName "QuickSessionCollection" `
    -Alias "app-launcher" `
    -DisplayName "app-launcher" `
    -FilePath "C:\12Port-RDSApps\app-launcher\app-launcher.exe" `
    -ShowInWebAccess $true `
    -CommandLineSetting Allow

-CommandLineSetting Allow is required: it lets the Access Broker pass connection parameters to the launcher. Make sure -FilePath matches where you copied the executable.

You publish the launcher once. Every application in the INI is reached through this one RemoteApp.


Configuring applications

INI structure

[launcher]
; global settings, also defaults for every application section

[my-app]
; one application

[my-app-variant]
base=my-app
; a variation of my-app; only what differs is listed here
  • [launcher] is reserved for global settings. It can't be used as an application name.
  • Every other section is an application. Its name is what the asset's Script field contains. (The asset field Script picks a section; the script= key inside that section holds the sign-in sequence.)
  • base=<section> copies every key the section doesn't set itself from another section. A base section can have its own base, up to 10 levels deep.
  • Lookup order for any key: the application section, then its base sections, then [launcher], then the built-in default.

Application settings

Key Required Description
exe Yes Program to start: a full path, or a bare name such as msedge.exe, which is looked up in the Windows App Paths registry.
args No Command-line arguments. {var:Field} is replaced with the asset field value (see Arguments).
script No Sign-in sequence (see Script reference).
workdir No Working directory. Defaults to the folder of exe.
kill-existing No Process name, such as msedge.exe, to close in the current user's session before starting. Processes in other users' sessions are never touched.
exit-watch No Window to watch after sign-in. When no window matching it is left, exit-action runs. See Session exit.
exit-action No kill, logoff, or kill,logoff. Applies only with exit-watch.
disconnect-wait No Milliseconds a session may stay disconnected before exit-action runs. Default 0: act immediately. See Session exit.

Global settings and timing

These go in [launcher] and can be overridden in any application section.

Key Default Description
debug false Shows the received parameters at startup (secret fields masked) and sends each script step to the 12Port server log.
winwait-timeout 60000 Default timeout, in milliseconds, for {winwait}, {ifwin} and {abortif}.
focus-wait 500 Pause, in milliseconds, after a window is activated.
send-key-delay 40 Delay, in milliseconds, between typed characters. Increase it for applications that drop characters.
send-key-down-delay 5 How long, in milliseconds, each key is held down.

Arguments

args can reference any asset field passed in Remote Arguments:

args=/plugin=5250 /system={var:Host} /port={var:Port} /ssl=1

For security, a value substituted into args must not contain spaces or double quotes. Otherwise the launcher refuses to start and shows an error. This stops an asset field from adding extra command-line switches. URLs must therefore be URL-encoded, for example %20 instead of a space.

Quote a placeholder in args when the program expects a quoted value: --new-window "{var:Host}".


Script reference

A script is a list of actions. Each action is enclosed in braces, and actions are separated by commas. They run left to right.

script={winwait:Sign in},{var:User},{TAB},{var:Password},{ENTER}

Actions

Action Description
{var:Field} Types the value of an asset field exactly as stored; special characters are typed literally.
{text:literal} Types fixed text exactly as written, for example a menu choice: {text:21}. Commas are allowed inside: {text:a, b}.
{TAB}, {ENTER}, {ESC}, {SPACE}, {BACKSPACE}, {DEL}, {INS}, {HOME}, {END}, {PGUP}, {PGDN}, {UP}, {DOWN}, {LEFT}, {RIGHT}, {F1} … {F24} Presses the key. Add a count to repeat: {TAB 3}.
{send:keys} Sends keys in AutoIt Send notation, for key combinations: {send:+{TAB}} is Shift+Tab and {send:^a} is Ctrl+A.
{wait:ms} Pauses for the given number of milliseconds.
{winwait:window} or {winwait:window,ms} Waits until the window exists, then activates it. Stops the script if it doesn't appear in time.
{winmax:window} or {winmax:window,ms} Waits for the window like {winwait}, then maximizes it. In a RemoteApp session the window fills the user's screen. Use it instead of a launch option: many applications, including Java applications and Edge, ignore the "start maximized" request and restore their own saved size.
{winactivate:window} Brings an existing window to the front. Stops the script if the window isn't found within 2 seconds.
{ifwin:window,ms} … {endif} Runs the enclosed actions only if the window appears within the time; otherwise skips to {endif}. If it appears, it is activated. Blocks can be nested.
{abortif:window,ms} Waits up to ms. If the window appears, the script stops; otherwise it behaves like {wait:ms}.

Field names in {var:} are case-sensitive and must match the asset field names exactly.

Window specifications

Wherever an action takes a window, you can use:

Form Matches
Sign in Any window whose title contains this text
[CLASS:SunAwtFrame] Windows of this window class
[REGEXPTITLE:^A - .*] Title matching a regular expression

Window classes are more reliable than titles when the title changes, as with browser tabs. To find a window's title and class, run AutoIt Window Info (Au3Info.exe, included with AutoIt) on the RDS server and point it at the window.

Writing reliable scripts

  • Wait for windows instead of sleeping. {winwait:[CLASS:...]} continues as soon as the window is there and fails clearly when it isn't. A fixed {wait:20000} is slow when the application is fast and too short when it is slow.
  • Use {abortif} after every submit. It replaces the pause you would put there anyway. If sign-in is rejected, the script stops instead of typing the next field, possibly a password, into an error dialog or an unrelated window.
  • Use {ifwin} for steps that don't always appear, such as a sign-on dialog shown only when no credentials are cached.
  • Keep a {wait} where nothing on the screen can be detected. Terminal emulators draw sign-on screens and menus inside one window, so the screen content can't be watched. {wait} is the only option there.

When a script stops

If an action fails (window timeout, missing field, unknown action, or {abortif} triggered), the launcher:

  1. stops typing,
  2. writes the reason to the 12Port server log,
  3. unblocks user input, so the user can finish signing in manually,
  4. continues with session exit handling.

Session exit

Without exit-watch, the launcher exits as soon as the script finishes, and the application runs on by itself. This suits most applications.

With exit-watch, the launcher stays in the background. It notes which processes own the matching windows. It runs exit-action when either of these happens:

  • no window matching exit-watch is left, or
  • the RDS session is disconnected, for example because the user closed the remote session window without closing the application, and stays disconnected for disconnect-wait milliseconds (default 0, immediately).

Acting on disconnect matters for security. A disconnected session keeps running, and the next connection by the same account reattaches to it. Without this, the next user of that account would find the application still signed in. Set disconnect-wait, for example to 60000, only if users must be able to resume after short network interruptions.

The actions are:

  • kill ends the processes that owned the watched windows and the originally started process, together with their child processes.
  • logoff forces the RDS session to log off, even if an application refuses or hangs.

Use this for applications that leave background processes running after their window closes, so the RDS session doesn't stay open. Some Java applications behave this way. With logoff, a disconnected session is logged off rather than left waiting.

Disconnect handling works only while the launcher runs, that is, only with exit-watch. As a server-wide safeguard, also set the Remote Desktop Session Host policy Set time limit for disconnected sessions, so Windows logs off disconnected sessions even when no launcher is watching.

Watch the main window, not a dialog. The launcher acts only once the main window is gone, so a user who cancels an "Are you sure?" prompt keeps working.


Asset configuration

  1. Create an asset of type RDS Remote Application.
  2. Set Remote App to ||app-launcher, the published alias prefixed with ||.
  3. The asset type has a text field named Script, hidden by default. Unhide it and set it to the INI section name, for example acs-5250.
  4. Add any other fields the section uses. Secrets such as passwords, TOTP secrets or secondary credentials go in fields marked Secured. For TOTP, add a field of type TOTP with Algorithm SHA1.
  5. Set Remote Arguments to the comma-separated list of fields the section uses, including Script, for example Script,Host,Port,User,Password. Only listed fields reach the launcher.
  6. Under Member Assets, add the RDS Host Server asset with the Member role. Sessions can't start without it.

Never put credentials in the INI file. Every credential the script types must come from a secured asset field through {var:...}. The INI is a plain text file on the RDS server, readable by its users.


Examples

Web login page in Microsoft Edge

Remote Arguments: Script,Host,User,Password,TOTP, where Host is the login page URL.

[web-portal]
exe=msedge.exe
args=--new-window "{var:Host}" --no-first-run --disable-features=RestoreSessionState
kill-existing=msedge.exe
script={winwait:[CLASS:Chrome_WidgetWin_1]},{wait:2000},{var:User},{wait:1000},{TAB},{wait:1000},{var:Password},{wait:1000},{ENTER},{wait:1000},{var:TOTP},{wait:1000},{ENTER}

[web-portal-no-mfa]
base=web-portal
script={winwait:[CLASS:Chrome_WidgetWin_1]},{wait:2000},{var:User},{wait:1000},{TAB},{wait:1000},{var:Password},{wait:1000},{ENTER}

Before using Edge, sign in to the RDS server with the Host Server account and turn off: - Show tab search, at edge://settings/appearance, which can take keyboard focus, - Offer to save passwords, which can interrupt the sequence.

IBM i Access Client Solutions 5250

Remote Arguments: Script,Host,Port,User,Password

[acs-5250]
exe=C:\Users\Public\IBM\ClientSolutions\Start_Programs\Windows_x86-64\acslaunch_win-64.exe
args=/plugin=5250 /system={var:Host} /port={var:Port} /wide /ssl=1
exit-watch=[CLASS:SunAwtFrame]
exit-action=kill,logoff
script={winwait:[CLASS:SunAwtFrame]},{ifwin:Signon to IBM i,15000},{var:User},{TAB},{var:Password},{ENTER},{abortif:Error Message,6000},{endif},{winactivate:[CLASS:SunAwtFrame]},{var:User},{TAB},{var:Password},{ENTER},{abortif:Error Message,3000}

The script signs in twice: first in the Java Signon to IBM i dialog, if ACS shows it, then on the 5250 sign-on screen.

To use a specific Java runtime, put -vm in front of the other arguments:

args=-vm "C:\Path\To\jre" /plugin=5250 /system={var:Host} /port={var:Port} /wide /ssl=1

Use -vm if ACS fails to start with the system Java. A missing runtime library, for example, shows up as a Java UnsatisfiedLinkError in the debug log.

Variation with a fixed port

[acs-5250-prod]
base=acs-5250
args=/plugin=5250 /system={var:Host} /port=992 /wide /ssl=1

HMC 5250 console with a second set of credentials

Remote Arguments: Script,Host,Port,User,Password,HmcUser,HmcPassword, with HmcPassword as a secured field. Create the HmcUser and HmcPassword fields in the RDS Remote Application asset type.

[acs-5250-hmc]
base=acs-5250
script={winwait:[CLASS:SunAwtFrame]},{wait:20000},{text:21},{ENTER},{wait:500},{var:HmcUser},{TAB},{var:HmcPassword},{ENTER},{abortif:Error Message,1000},{text:1},{ENTER},{wait:1000},{text:1},{ENTER},{wait:1000},{var:User},{TAB},{var:Password},{ENTER}

The {wait} values depend on how quickly the host responds. Tune them per environment.


Troubleshooting

Turn on diagnostics

  1. Set debug=true in [launcher], or only in the affected application section.
  2. In 12Port, set these loggers to Debug:
  3. com.otna.proxy.rdp.pipeline.handler.RemoteAgentHandler for sessions through the RDP proxy,
  4. com.otna.gw.filter.RemoteAgentFilter for browser (HTML5) sessions.
  5. Start a session. The launcher shows the received parameters for 10 seconds, and the server log records each script step, for example step 7: abortif:Error Message,6000. Typed values are never logged.

In the startup window, fields whose names contain pass, totp, secret, token, key or pin are shown as ****. Every other field is shown in clear text, so turn debug off again when you're done.

Common problems

Symptom Cause and fix
"Application '…' is not configured on this server." The asset's Script field is empty, misspelled, or Script is missing from Remote Arguments. Section names must match exactly.
"Cannot start '…': asset field 'X' contains whitespace or quotes" A value used in args contains a space or ". Fix the field value, or URL-encode it.
"Cannot start '…': missing asset field 'X' used in args" Add X to the asset's Remote Arguments.
Log: window '…' did not appear The application started slowly or the window spec doesn't match. Check the title or class with AutoIt Window Info; increase the timeout with {winwait:spec,ms}.
Log: missing asset field 'X' A {var:X} field isn't in Remote Arguments, or its name differs in case.
Log: unknown action '{…}' Typo in the script, or an unsupported key name. Use {send:…} for key combinations.
Characters are missing or out of order The application can't keep up. Raise send-key-delay, for example to 80.
Keystrokes go to the wrong window Add {winwait} or {winactivate} before typing, and {abortif} after each submit.
The RDS session stays open after the user closes the application Background processes are still running. Add exit-watch for the main window and exit-action=kill,logoff.

Testing a section without the broker

You can run a section directly on the RDS server. Encode the parameters as Base64 Name=Value lines and pass them as the first argument:

$p = "Script=acs-5250`nHost=myhost.example.com`nPort=992`nUser=TESTUSER`nPassword=..."
$b = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($p))
& C:\12Port-RDSApps\app-launcher\app-launcher.exe $b

Base64 is not encryption. Values passed this way can be read in the process list and in your console history. Test only with throwaway accounts, and clear your PowerShell history afterwards.


Limitations

  • The launcher works through keystrokes and window detection. It can't read text inside a window. On terminal screens and single-window applications, sign-in errors shown inside the window aren't detected; only separate dialog windows are.
  • Keystrokes go to the window that has focus. Scripts should activate the target window before typing.
  • One script runs per session. The launcher doesn't sign in again after a session times out inside the application.