Pete Hinchley: Use Thinstation to Build a Network-Bootable RDP-Enabled Thin Client Image

In this post I will provide an overview of the steps required to build a PXE-enabled RDP client using Thinstation.

Overview

Thinstation is an open source thin client-solution based on Crux Linux. The framework can be used to build streamlined network-bootable images that support a range of connectivity options, such as Microsoft RDP, Citrix ICA, and VMware View.

In this post I will describe how to configure two thin-client images. The first image will allow users to establish a Remote Desktop session to a Windows Server using the FreeRDP client. The second image will allow users to launch individual applications published using RemoteApp (also served via a FreeRDP client). The applications will be accessed via a Remote Desktop Web Access Portal using Google Chrome running in "kiosk" mode.

Requirements

The scenarios presented in this article require the following:

As an alternative to (standalone) WDS, you could also use a PXE-enabled Microsoft SCCM distribution point, a dedicated deployment of PXELINUX, or some other PXE service provider. You can also switch out IIS for another web server if you prefer.

Note: If you use a PXE-enabled distribution point, ensure that you don't enable unknown computer support, as this will prevent SCCM from passing all PXE requests through to WDS (even when using DHCP option 67 to target a specific boot program).

Windows Deployment Services

Even though we are using WDS, we still require access to components of PXELINUX to support the network boot of a Linux image. The relevant files can be obtained via syslinux. We will use version 4.07 (as external dependencies are required with later releases).

Extract the syslinux package to C:\syslinux-4.07 on the WDS server, and perform these steps:

  1. Copy the following files to C:\RemoteInstall\Boot\x64:

    • C:\syslinux-4.07\core\pxelinux.0
    • C:\syslinux-4.07\com32\menu\vesamenu.c32
    • C:\syslinux-4.07\com32\chain\chain.c32
    • C:\syslinux-4.07\memdisk\memdisk
  2. Rename C:\RemoteInstall\Boot\x64\pxelinux.0 to pxelinux.com.
  3. Copy C:\RemoteInstall\Boot\x64\abortpxe.com to abortpxe.0.
  4. Copy C:\RemoteInstall\Boot\x64\pxeboot.n12 to pxeboot.0.
  5. Repeat the above steps, replacing all references to x64 with x86.
  6. Create a folder named C:\RemoteInstall\Boot\pxelinux.cfg (yes, it is a folder).
  7. From an elevated command prompt, change into C:\RemoteInstall\Boot\x64 and create a junction: mklink /J pxelinux.cfg C:\RemoteInstall\Boot\pxelinux.cfg
  8. Repeat the previous step from C:\RemoteInstall\Boot\x86.
  9. Create a folder named C:\RemoteInstall\Images\Linux.
  10. From an elevated command prompt, change into C:\RemoteInstall\Boot\x64 and create a junction: mklink /J Linux C:\RemoteInstall\Images\Linux
  11. Repeat the previous step from C:\RemoteInstall\Boot\x86.
  12. Create a file named default under C:\RemoteInstall\Boot\pxelinux.cfg and add the following content:

    DEFAULT       RDP
     PROMPT       0
     NOESCAPE     1
     ALLOWOPTIONS 0
    LABEL RDP
     MENU DEFAULT
     KERNEL /Linux/vmlinuz
     APPEND initrd=/Linux/initrd splash=silent,theme:default load_ramdisk=1 ramdisk_blocksize=4096 root=/dev/ram0 ramdisk_size=786432 loglevel=3 console=tty1 vt.global_cursor_default=0 LM=3

To switch the default boot programs used by WDS to PXELINUX, you could run the following commands from an elevated command prompt on the WDS server:

wdsutil /set-server /bootprogram:boot\x86\pxelinux.com /architecture:x86
wdsutil /set-server /N12bootprogram:boot\x86\pxelinux.com /architecture:x86
wdsutil /set-server /bootprogram:boot\x64\pxelinux.com /architecture:x64
wdsutil /set-server /N12bootprogram:boot\x64\pxelinux.com /architecture:x64

This step isn't required if you are using a PXE-enabled distribution point, as SCCM takes over WDS and ignores the natively configured settings. Nor is the step required if you will use DHCP options 66 and 67 to specifically direct a client to use the PXELINUX boot program (more on this later).

Note: The following commands will restore the default WDS boot programs (in case you need to roll back):

wdsutil /set-server /bootprogram:boot\x86\pxeboot.com /architecture:x86
wdsutil /set-server /N12bootprogram:boot\x86\pxeboot.n12 /architecture:x86
wdsutil /set-server /bootprogram:boot\x64\pxeboot.com /architecture:x64
wdsutil /set-server /N12bootprogram:boot\x64\pxeboot.n12 /architecture:x64

Build Environment

The next step is to configure the environment we will use for creating our thin-client image. Fortunately, Thinstation comes with a pre-configured build environment called DevStation (direct download of approx. 180MB).

Note: The notes in this article were based on DevStation 5.4.2.

I created a virtual machine on my Mac using VMware Fusion for hosting DevStation. I used an x64 Linux 2.6.x operating system profile with the following specifications: 2 cores, 2GB RAM, and a 20GB disk. You can of course achieve the same effect with another hypervisor, such as VirtualBox, VMware Workstation or Hyper-V.

After booting the virtual machine from the downloaded media, follow these steps to install DevStation (internet access is required):

  1. Acknowledge the installation notes.
  2. Agree to use disk /dev/sda.
  3. Set a timezone. I used Australia/Sydney.
  4. Set a locale. I used en_US.
  5. Agree to erasing the disk.
  6. Wait until the installation is completed (approx. 20 minutes).
  7. Acknowledge the post-installation notes.

After rebooting the virtual machine (you will be logged in automatically), double-click the Terminal Chroot shortcut on the desktop and then change into the /build directory.

At this point we are ready to create a custom thin-client image.

Device Drivers

The first step is to identify the device drivers required by our target platform (i.e. the system that will run the thin-client). In the context of this article, I will assume the client is a Hyper-V virtual machine, but in practice, you would probably use a lightweight desktop or thin-client terminal.

To identify the specific drivers required by our client we will:

  1. Build an image that includes all drivers natively supported by Thinstation.
  2. Boot the image on our target device and run a script that identifies which of the available drivers are loaded.
  3. Use the information collected in the previous step to build a hardware profile within DevStation.
  4. Build a new image based on the custom hardware profile.

To start, from our existing Terminal session, run the following two commands to create a clean build configuration:

rm build.conf
cp build.con.example build.conf

Edit build.conf (use vi or vim) and uncomment (remove the #) from the line containing package extensions-x. Save the file and use the following command to build an image that includes all available drivers: ./build --allmodules

Note: Enter Y when prompted to install Chrome.

Although we intend to use WDS to boot our thin-client image, in this instance we will actually use the PXE server built into DevStation. This is because we need the hardware profile data to be copied to DevStation, and the script we will use to collect the data is preconfigured to post the results back to the PXE server that booted the client.

To direct the client to use the PXE server on the DevStation system, create a DHCP reservation for the client and configure DHCP options 66 and 67. Set option 66 to the name or IP address assigned to DevStation, and set option 67 to boot/pxelinux/pxelinux.0.

Note: To get the IP address of the DevStation system, run ifconfig from the Terminal.

There are a few additional requirements:

Now boot the client. If you've configured everything correctly, the system should receive an IP address and immediately commence booting from the network. After a short delay during which the image is downloaded, you should see the default Thinstation desktop.

Before we proceed further, switch back to DevStation, and from the Terminal, run the following command to allow write access to the TFTP root directory: chmod 777 /build/boot-images/pxe

Now, back on the client, assuming it booted as planned, open a Terminal session (from the Applications menu, select Utilities and Terminal Emulator) and run the following command to generate a hardware profile: /bin/hwlister.sh

The results should be posted back to the TFTP root on the DevStation system. In my case, a file named module.list was created, but you may also see package.list and/or firmware.list.

At this point you can shutdown the client (e.g. shutdown -halt).

On the DevStation system, run the following commands to create a new hardware profile named Hyper-V based on the collected data (replace Hyper-V with a name representing your target device model):

mkdir /build/machine/Hyper-V
cd /build/machine/Hyper-V
mv /build/boot-images/pxe/*.list .
chmod 755 /build/boot-images/pxe

We will modify the build to incorporate the new hardware profile in the next section.

RDP Client

We will now modify the build to create a minimal RDP-enabled thin-client.

Change back to the /build directory and edit build.conf:

The remainder of the configuration will occur in a file named thinstation.conf.buildtime. First, let's take a backup of the original: cp thinstation.conf.buildtime thinstation.conf.buildtime.original

Now edit thinstation.conf.buildtime and:

SESSION_0_TITLE="RDP"
SESSION_0_TYPE=freerdp
SESSION_0_FREERDP_SERVER="rdp.lab.hinchley.net"
SESSION_0_FREERDP_OPTIONS="/d:'lab.hinchley.net' /u:'' /bpp:16 /cert-ignore -sec-nla"
FASTBOOT_URL="http://10.0.0.100/"

Note: Replace rdp.lab.hinchley.net with the name of the target RDP server, replace lab.hinchley.net with the name of your Active Directory domain (against which users will authenticate), and replace 10.0.0.100 with the name or IP address of your IIS web server.

By default, the FreeRDP client will attempt to automatically connect to the target RDP server. To prevent this behaviour, and to show an X-window logon dialog instead, run the following command: touch /build/packages/freerdp/etc/cmd/freerdp.getuser

We will now apply some simple branding. Firstly, to use a custom splash screen (shown during boot), replace the file named silent.jpg within every folder under /build/utils/tools/splash/default (i.e. there is a separate folder for every supported resolution). The dimensions of the image should be based upon the name of the folder.

Next, to customise the logo displayed on the RDP logon dialog, change the following image: /build/packages/freerdp/lib/icons/hicolor/32x32/apps/freerdp.png. Note: The dimensions of the image must be 32x32 pixels.

Finally, to customise the title of the RDP logon dialog (changing from "Preparing to Run FreeRDP"), edit /build/packages/base/etc/dialog.functions and replace Preparing to run '$DIALOG_TITLE' with something appropriate. I used Connect to LAB.

We are now ready to build the thin-client image. From the /build directory run: ./build

When the build completes, copy the initrd and vmlinuz files from /thinstation/build/boot-images/pxe/boot/ to C:\RemoteInstall\Boot\x64\Linux on the WDS server. The vmlinuz file will be approximately 10MB.

Note: If using File Manager on the DevStation system to copy the files, enter a path of smb://wds/c$/RemoteInstall/Boot/x64/Linux (where wds is the address of your WDS server).

Fast Boot and IIS

In the previous section, we enabled support for fastboot in build.conf. This feature allows us to download a significant portion of the thin-client image via HTTP (instead of TFTP) - thereby reducing boot time. For this to work, there are a few prerequisites:

RDP Server

Our thin-client will use the FreeRDP client to connect to our target RDP server. To ensure that users connecting via the client are prompted to change their password when it is expired, we will need to disable NLA on the RDP server. This setting can be configured by opening Control Panel, System and Security, System, selecting Remote Settings, and then deselecting the option to Allow connections only from computers running Remote Desktop with Network Level Authentication.

PXE Configuration

We are almost ready to boot the thin-client. If you are using a standard WDS instance, it should be sufficient to just remove DHCP options 66 and 67 from the client reservation previously created. However, if you are using a PXE-enabled distribution point, or you just want to be explicit (and avoid DevStation from responding ahead of WDS), reconfigure DHCP options 66 and 67 so that option 66 references the name or IP address of the WDS server, and option 67 is set to boot/x64/pxelinux.com.

Testing

Start the client. Just as before, it should obtain an IP address and then immediately initiate a network boot. Unlike before, the TFTP download will complete in only a few seconds, after which the custom splash screen will be displayed, and then the remainder of the thin-client image will be downloaded from IIS over HTTP.

If everything works as expected, the splash screen will be replaced by a single RDP logon dialog. Enter the username and password of a user authorised for access to the configured server, and click OK (the domain should not be entered, as this was configured within thinstation.conf.buildtime). With any luck, you will be seamlessly authenticated to a full-screen session on the server.

Remote Desktop Web Access

Now that we have a working RDP thin-client solution, let's create another image that can be used to connect to a Microsoft Remote Desktop Server farm.

This is a summary of the approach:

Unfortunately, as of the present time, there are a few shortcomings in this solution:

  1. I wasn't able to enable single sign-on between the Remote Desktop Web Access Portal and a RemoteApp session. Nor was I able to enable single sign-on between RemoteApp sessions. i.e. A user must authenticate to retrieve the list of published applications, and then re-authenticate whenever an application is launched.
  2. Due to a bug in the FreeRDP client packaged with Thinstation 5.4.2, the mouse did not work properly within a RemoteApp session. Note: This issue could be addressed by using the most recent version of FreeRDP (i.e. compiling form the latest source).

Note: This article will not describe the steps required to setup or configure a Remote Desktop solution; only the steps required to configure the Thinstation image are presented.

Let's start by saving the previously modified build.conf and thinstation.conf.buildtime configuration files:

cd /build
cp build.conf build.conf.rdp
cp thinstation.conf.buildtime thinstation.conf.buildtime.rdp

Now edit build.conf and under #!Applications uncomment #package chrome.

Edit thinstation.conf.buildtime and replace the SESSION_0 lines previously added with the following:

SESSION_0_TITLE="KIOSK"
SESSION_0_TYPE=chrome
SESSION_0_CHROME_HOMEPAGE="https://rdp.lab.hinchley.net/RDWeb"
SESSION_0_CHROME_OPTIONS="--ignore-certificate-errors --kiosk --start-full-screen --test-type"

You will also need to replace https://rdp.lab.hinchley.net/RDWeb with the URL of your Remote Desktop Web Access server.

The final step is to configure Chrome to automatically invoke the FreeRDP client when an RDP file is downloaded. We can do this by editing /build/packages/gnome-core/bin/xdg-open and adding the following code immediately under the line which executes detectDE:

if test ${url##*.} = "rdp"; then
  /bin/xfreerdp "$url" /cert-ignore /sec:rdp
  exit 0
fi

We also need to ensure the above association is invoked automatically (to avoid the user from being prompted to open RDP files). This is controlled via a preference in Chrome which can be configured by creating a file named /build/packages/chrome/etc/chrome/Default/Preferences with the following content:

{"account_id_migration_state":2,"account_tracker_service_last_update":"13097285309636958","download":{"directory_upgrade":true,"extensions_to_open":"rdp"}}

To build the new image, rerun: ./build

Note: The introduction of Chrome will add approximately 70MB to the image.

Dual Boot

At this point we could deploy our new image by copying the updated initrd to the WDS server, and lib.squash to the web server (vmlinuz won't be changed by the rebuild). However, we can also make a few changes to our WDS configuration to allow users to boot either image.

Let's start by editing C:\RemoteInstall\Boot\x64\pxelinux.cfg\default on the WDS server and replacing the existing content with the following:

DEFAULT       vesamenu.c32
 PROMPT       0
 NOESCAPE     1
 ALLOWOPTIONS 0
 TIMEOUT 300
 MENU MARGIN 10
 MENU ROWS 16
 MENU TABMSGROW 21
 MENU TIMEOUTROW 26
 MENU COLOR BORDER 30;44 #20ffffff #00000000 none
 MENU COLOR SCROLLBAR 30;44 #20ffffff #00000000 none
 MENU COLOR TITLE 0 #ffffffff #00000000 none
 MENU COLOR SEL 30;47 #40000000 #20ffffff
 MENU TITLE Boot Menu
 #---
 LABEL RDP
 MENU DEFAULT
 KERNEL /Linux/vmlinuz
 APPEND initrd=/Linux/initrd splash=silent,theme:default load_ramdisk=1 ramdisk_blocksize=4096 root=/dev/ram0 ramdisk_size=786432 loglevel=3 console=tty1 vt.global_cursor_default=0 LM=3
 #---
 LABEL KIOSK
 MENU LABEL Kiosk
 KERNEL /Linux/vmlinuz
 APPEND initrd=/Linux/initrdk splash=silent,theme:default load_ramdisk=1 ramdisk_blocksize=4096 root=/dev/ram0 ramdisk_size=786432 loglevel=3 console=tty1 vt.global_cursor_default=0 LM=3

Now, go back to DevStation and edit thinstation.conf.build and update FASTBOOT_URL to end in kiosk (for example: http://10.0.0.100/kiosk), and then rebuild the solution.

After the build completes, rename /thinstation/build/boot-images/pxe/boot/initrd to initrdk and copy it to the following folder on the WDS server: C:\RemoteInstall\Boot\x64\Linux

Also copy lib.squash to a new folder named C:\inetpub\wwwroot\kiosk on the web server.

Now when you boot the client you should see a boot menu with two options. The first, named RDP, will initiate the boot of our first image, and the second, name Kiosk, will initiate the build of the image with Chrome. The first option is configured as the default, and will be automatically invoked after 30 seconds.

Try booting the client, select Kiosk from the boot menu, and cross your fingers. If all goes well, Chrome should open fullscreen at the logon page of your Remote Desktop Web Access Portal. Log in, and open an application. Assuming you have disabled NLA on your farm, after you (re)authenticate, the requested application will be displayed.

Extend Boot Menu

It's possible to extend the boot menu (by modifying C:\RemoteInstall\Boot\x64\pxelinux.cfg\default) to include additional options, such as booting an ISO, or a binary image, passing through to WDS (if your PXE server is using WDS), or cancelling the PXE request and booting off the local hard drive. The following example configuration includes all of these options:

DEFAULT       vesamenu.c32
 PROMPT       0
 NOESCAPE     1
 ALLOWOPTIONS 0
 TIMEOUT 300
 MENU MARGIN 10
 MENU ROWS 16
 MENU TABMSGROW 21
 MENU TIMEOUTROW 26
 MENU COLOR BORDER 30;44  #20ffffff #00000000 none
 MENU COLOR SCROLLBAR 30;44 #20ffffff #00000000 none
 MENU COLOR TITLE 0     #ffffffff #00000000 none
 MENU COLOR SEL   30;47   #40000000 #20ffffff
 #MENU BACKGROUND MyMenuBackgroundPicture640x480.jpg
 MENU TITLE Boot Menu
 #---
 LABEL RDP
 MENU DEFAULT
 KERNEL /Linux/vmlinuz
 APPEND initrd=/Linux/initrd splash=silent,theme:default load_ramdisk=1 ramdisk_blocksize=4096 root=/dev/ram0 ramdisk_size=786432 loglevel=3 console=tty1 vt.global_cursor_default=0 LM=3
 #---
 LABEL KIOSK
 MENU LABEL Kiosk
 KERNEL /Linux/vmlinuz
 APPEND initrd=/Linux/initrdk splash=silent,theme:default load_ramdisk=1 ramdisk_blocksize=4096 root=/dev/ram0 ramdisk_size=786432 loglevel=3 console=tty1 vt.global_cursor_default=0 LM=3
 #---
 LABEL wds
 MENU LABEL Windows Deployment Services
 KERNEL pxeboot.0
 #---
 LABEL memtest86+bin
 MENU LABEL Memtest86+ Binary
 KERNEL /Linux/memtest86+
 #---
 LABEL memtest86+iso
 MENU LABEL Memtest86+ ISO
 KERNEL /Linux/memtest86+.iso
 #---
 LABEL Abort
 MENU LABEL AbortPXE
 Kernel abortpxe.0
 #---
 LABEL local
 MENU DEFAULT
 MENU LABEL Boot from Harddisk
 LOCALBOOT 0
 Type 0x80

That will do for today. Drop me an email if you have any questions.

Addendum

A few random dot points regarding enhancements implemented to enable network-based dynamic configuration (including the use of multiple monitors and low resolution displays):

USE_XRANDR=TRUE
XRANDR_OPTIONS="--output DisplayPort-0 --output DisplayPort-1 --left-of DisplayPort-0"
SESSION_0_TITLE="RDP"
SESSION_0_TYPE=freerdp
SESSION_0_FREERDP_SERVER="rdp.lab.hinchley.net"
SESSION_0_FREERDP_OPTIONS="/d:'lab.hinchley.net' /u:'' /cert-ignore /sec:tls /multimon"
# HOST  MAC             GROUPS  COMMENTS
Ts1     7cd30a1da71f    BLIND   # Force low resolution display.
SCREEN_RESOLUTION="800x600"
LABEL RDP
MENU DEFAULT
KERNEL /Linux/vmlinuz
APPEND initrd=/Linux/initrd splash=silent,theme:default load_ramdisk=1 ramdisk_blocksize=4096 root=/dev/ram0 ramdisk_size=786432 loglevel=3 console=tty1
vt.global_cursor_default=0 LM=3 FASTBOOT_URL=http://10.0.0.10/

Troubleshooting

In a "real world" deployment of the Thinstation solution I've described in this post, I encountered a couple of issues when using a monitor connected to the terminal via a KVM switch box with a DisplayPort connector:

  1. When the KVM was used to switch between the terminal and another system, and then back again, the monitor would enter a disconnected state.
  2. (After resolving issue 1) When the KVM was used to switch between the terminal and another system, and then back again, the mouse cursor would disappear. The mouse was active (the system would respond to mouse movement and button clicks), but the cursor was invisible. This issue did not occur when using dual monitors.

The solution to both issues was to run a custom script when the terminal detected a change to the status of the DisplayPort (i.e. after the monitor(s) were reconnected when the KVM was switched back to the terminal).

Within DevStation, under /build/packages/automount/etc/udev/rules.d I created a file named 70-monitor.rules with the following content:

KERNEL=="card0", SUBSYSTEM=="drm", ENV{DISPLAY}=":0", ENV{XAUTHORITY}="/root/.Xauthority", RUN+="/etc/udev/scripts/monitor.sh"

Then, under /build/packages/automount/etc/udev/scripts I created a file named monitor.sh with the following content:

#! /bin/sh
xrandr --auto
xrandr --output DisplayPort-0 --output DisplayPort-1 --left-of DisplayPort-0
chvt 1 && chvt 5

Note: Use chmod 755 monitor.sh to make the script executable.

This script uses xrandr with the --auto switch to detect (and activate) the monitor(s) connected to the terminal via the KVM. It then reconfigures the dual screen layout previously configured at boot (this works when using either one or two monitors). Finally, the script uses chvt to swtich between TTY1 and 5, which was sufficient to force the cursor to reappear. The udev rule, and corresponding script, execute in real-time, and are unnoticed by the user.