Tunlr enable/disable script for Microsoft Surface

I recently bought a Microsoft Surface and I have been wanting to watch a few programmes on BBC iPlayer whilst out of the country for Christmas. I discovered Tunlr – a free media proxy service which allows access to Hulu and iPlayer regardless of geolocation. However, editing DNS server settings by hand is time consuming and awkward without using the trackpad, so I wanted a script to automate the task. This will also work for other proxy services such as unblock-us.com – just replace the DNS IPs in the script. I had previously written a quick script for changing IP configuration which used netsh commands but these don’t work on Windows RT. Some other PowerShell methods I found weren’t supported either but I did find new network settings cmdlets for the purpose that were added in Windows 8/RT.

The next problem was elevation to get sufficient rights to change the network settings. It transpires that the PowerShell and VBScript environments are heavily restricted in Windows RT, which prevents auto-prompting for elevation. Fortunately Windows RT does allow Run as Administrator from the right-click menu for .cmd scripts. If you’re using touch control, you just touch and hold then release for the right-click. The script will remind you if you forget to do this. Hover your mouse over the top right corner of the script below, and use the View Source button to save the following to your desktop as Tunlr.cmd:

@echo off

::Use Tunlr to watch streaming TV services regardless of geolocation
::Tunlr DNS servers redirect requests for well-known services via Tunlr's proxy servers
::Tunlr should only be used while watching streams to reduce server load
::More details at http://tunlr.net/

::Elevation cannot be automated on Windows RT since object creation is disabled for PowerShell and VBScript
::http://stackoverflow.com/questions/13504499/windows-rt-powershell-permissiondenied-on-new-object

ipconfig /all | find "142.54.177.158" > nul && (
  echo Disabling Tunlr...
  PowerShell -Command Set-DnsClientServerAddress -InterfaceAlias "WiFi" -ResetServerAddresses || (
    echo Right-click and re-run this script as Administrator
    pause
  )
) || (
  echo Enabling Tunlr...
  PowerShell -Command Set-DnsClientServerAddress -InterfaceAlias "WiFi" -ServerAddresses 142.54.177.158,209.141.56.79 || (
    echo Right-click and re-run this script as Administrator
    pause
  )
)

UAC elevation for Windows batch script

I recently needed to make an interactive batch script elevate for admin privileges. I found an example script by jagaroth, and then refined it to make it even more compact. It only writes out one temporary script file, and passes the rest of the required variables on the command line. It can cope with paths containing spaces. It was something of a shell escaping nightmare as you can see from line 14!

@echo off

::Windows XP doesn't have UAC so skip
for /f "tokens=3*" %%i in ('reg query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion" /v ProductName ^| Find "ProductName"') do set WINVER=%%i %%j 
echo %WINVER% | find "XP" > nul && goto commands

::prompt for elevation
if "%1" == "UAC" goto elevation
(
  echo Set objShell = CreateObject^("Shell.Application"^)
  echo Set objFSO = CreateObject^("Scripting.FileSystemObject"^)
  echo strPath = objFSO.GetParentFolderName^(WScript.ScriptFullName^)
  echo If objFSO.FileExists^("%~0"^) Then
  echo   objShell.ShellExecute "cmd.exe", "/c """"%~0"" UAC ""%~dp0""""", "", "runas", 1
  echo Else
  echo   MsgBox "Script file not found"
  echo End If
) > "%TEMP%\UAC.vbs"
cscript //nologo "%TEMP%\UAC.vbs"
goto :eof
:elevation
del /q "%TEMP%\UAC.vbs"

:commands
::navigate back to this script's home folder
%~d2
cd "%~p2"

::put your main script here
echo 1st arg: %1
echo 2nd arg: %2
pause

Cross-compiling FFmpeg for Serviio with shared libraries on Synology NAS (Intel, ARMv5, ARMv7, and QorIQ CPUs)

Update 2 – Updated to allow build for ARMv7 CPU in Synology products using Armada370 SoC (currently the DS213j).

Update – I discovered that the cross-compiled ARMv5 build of FFmpeg cannot encode AC3 audio without severe artifacts. After a very long time spent recompiling with different options, trying static builds etc. I finally found that the Synology cross toolchain (GCC 4.2.1) appears to cause the problem. Compiling natively on a bootstrapped ARMv5 Synology system with GCC 4.2.3 produces a working binary. However, I had to spend a long time re-working the method, particularly since you can’t run automake to build libshine on the Synology due to a dependency on a threaded version of Perl, which is missing from the Optware repo, and in turn is very tricky to compile. I have appended the ARM shared build guide to the end of this post.

This method will build FFmpeg with portable shared libraries (linked with relative paths) using a Ubuntu Desktop 12 VM. The >10MB size of the static executable is kind of getting out of hand especially on embedded systems with very limited RAM. Using shared libs means many concurrent instances of FFmpeg can use broadly the same memory footprint, and package distribution binaries can be smaller – our target systems already have libmp3lame, libz, libssl, libcrypto, libfreetype, libexpat, and libpthread which add up to several megabytes. I’m also guessing that the OS will decide when to unload them from RAM which could help when FFmpeg is being launched repeatedly in a short period of time.

Although the unmodified source code will compile successfully for QorIQ CPUs, the FFmpeg binary will core dump when running any command on a video file. I contacted Synology Support for help with this issue. Their developers mentioned that they had needed to make numerous patches to the source of the version they used for DSM, and they guessed that the following one in particular was likely to be needed. This produced a working binary.

--- ffmpeg/libavcodec/ppc/dsputil_ppc.c	2012-02-17 18:20:07.000000000 +0000
+++ ffmpeg-patched/libavcodec/ppc/dsputil_ppc.c	2012-10-18 22:15:50.740992688 +0100
@@ -116,7 +116,11 @@
 
     /* below the constraint "b" seems to mean "Address base register"
        in gcc-3.3 / RS/6000 speaks. seems to avoid using r0, so.... */
+    #if 1
+    __asm__ volatile("dcbz %0, %1" : : "r" (fakedata_middle), "r" (zero));
+    #else
     __asm__ volatile("dcbzl %0, %1" : : "b" (fakedata_middle), "r" (zero));
+    #endif
 
     for (i = 0; i < 1024 ; i ++) {
         if (fakedata[i] == (char)0)
 

Here is a patch to add libshine as an additional encoder to FFmpeg 1.1.1 which is particularly useful on ARMv5 CPUs which lack an FPU (shine is an integer maths encoder).

diff -rupN ffmpeg-1.1.1/configure ffmpeg-1.1.1-shine/configure
--- ffmpeg-1.1.1/configure	2013-01-20 01:06:39.000000000 +0000
+++ ffmpeg-1.1.1-shine/configure	2013-02-05 13:46:30.000000000 +0000
@@ -214,6 +214,7 @@ External library support:
   --enable-libpulse        enable Pulseaudio input via libpulse [no]
   --enable-librtmp         enable RTMP[E] support via librtmp [no]
   --enable-libschroedinger enable Dirac de/encoding via libschroedinger [no]
+  --enable-libshine        enable fixed-point MP3 encoding via libshine [no]
   --enable-libsoxr         enable Include libsoxr resampling [no]
   --enable-libspeex        enable Speex de/encoding via libspeex [no]
   --enable-libstagefright-h264  enable H.264 decoding via libstagefright [no]
@@ -1174,6 +1175,7 @@ CONFIG_LIST="
     libpulse
     librtmp
     libschroedinger
+    libshine
     libsoxr
     libspeex
     libstagefright_h264
@@ -1843,6 +1845,7 @@ libopus_decoder_deps="libopus"
 libopus_encoder_deps="libopus"
 libschroedinger_decoder_deps="libschroedinger"
 libschroedinger_encoder_deps="libschroedinger"
+libshine_encoder_deps="libshine"
 libspeex_decoder_deps="libspeex"
 libspeex_encoder_deps="libspeex"
 libstagefright_h264_decoder_deps="libstagefright_h264"
@@ -3843,6 +3846,7 @@ enabled libopus    && require_pkg_config
 enabled libpulse && require_pkg_config libpulse-simple pulse/simple.h pa_simple_new
 enabled librtmp    && require_pkg_config librtmp librtmp/rtmp.h RTMP_Socket
 enabled libschroedinger && require_pkg_config schroedinger-1.0 schroedinger/schro.h schro_init
+enabled libshine   && require_pkg_config shine shine/layer3.h L3_encode_frame
 enabled libsoxr    && require  libsoxr soxr.h soxr_create -lsoxr
 enabled libspeex   && require  libspeex speex/speex.h speex_decoder_init -lspeex
 enabled libstagefright_h264  && require_cpp libstagefright_h264 "binder/ProcessState.h media/stagefright/MetaData.h
@@ -4264,6 +4268,7 @@ echo "libopus enabled           ${libopu
 echo "libpulse enabled          ${libpulse-no}"
 echo "librtmp enabled           ${librtmp-no}"
 echo "libschroedinger enabled   ${libschroedinger-no}"
+echo "libshine enabled          ${libshine-no}"
 echo "libsoxr enabled           ${libsoxr-no}"
 echo "libspeex enabled          ${libspeex-no}"
 echo "libstagefright-h264 enabled    ${libstagefright_h264-no}"
diff -rupN ffmpeg-1.1.1/libavcodec/allcodecs.c ffmpeg-1.1.1-shine/libavcodec/allcodecs.c
--- ffmpeg-1.1.1/libavcodec/allcodecs.c	2013-01-06 21:53:29.000000000 +0000
+++ ffmpeg-1.1.1-shine/libavcodec/allcodecs.c	2013-02-05 13:48:04.000000000 +0000
@@ -469,6 +469,7 @@ void avcodec_register_all(void)
     REGISTER_ENCDEC (LIBGSM,            libgsm);
     REGISTER_ENCDEC (LIBGSM_MS,         libgsm_ms);
     REGISTER_ENCDEC (LIBILBC,           libilbc);
+    REGISTER_ENCODER(LIBSHINE,          libshine);
     REGISTER_ENCODER(LIBMP3LAME,        libmp3lame);
     REGISTER_ENCDEC (LIBOPENCORE_AMRNB, libopencore_amrnb);
     REGISTER_DECODER(LIBOPENCORE_AMRWB, libopencore_amrwb);
diff -rupN ffmpeg-1.1.1/libavcodec/libshine.c ffmpeg-1.1.1-shine/libavcodec/libshine.c
--- ffmpeg-1.1.1/libavcodec/libshine.c	1970-01-01 00:00:00.000000000 +0000
+++ ffmpeg-1.1.1-shine/libavcodec/libshine.c	2013-02-05 13:49:39.000000000 +0000
@@ -0,0 +1,149 @@
+/*
+ * Interface to libshine for mp3 encoding
+ * Copyright (c) 2012 Paul B Mahol
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <shine/layer3.h>
+
+#include "libavutil/intreadwrite.h"
+#include "audio_frame_queue.h"
+#include "avcodec.h"
+#include "internal.h"
+#include "mpegaudio.h"
+#include "mpegaudiodecheader.h"
+
+#define BUFFER_SIZE (4096 * 20)
+
+typedef struct SHINEContext {
+    shine_config_t  config;
+    shine_t         shine;
+    uint8_t         buffer[BUFFER_SIZE];
+    int             buffer_index;
+    AudioFrameQueue afq;
+} SHINEContext;
+
+static av_cold int shine_encode_init(AVCodecContext *avctx)
+{
+    SHINEContext *s = avctx->priv_data;
+
+    if (avctx->channels <= 0 || avctx->channels > 2){
+        av_log(avctx, AV_LOG_ERROR, "only mono or stereo is supportedn");
+        return AVERROR(EINVAL);
+    }
+
+    L3_set_config_mpeg_defaults(&s->config.mpeg);
+    if (avctx->bit_rate)
+        s->config.mpeg.bitr = avctx->bit_rate / 1000;
+    if (L3_find_bitrate_index(s->config.mpeg.bitr) < 0) {
+        av_log(avctx, AV_LOG_ERROR, "invalid bitraten");
+        return AVERROR(EINVAL);
+    }
+    s->config.mpeg.mode = avctx->channels == 2 ? STEREO : MONO;
+    s->config.wave.samplerate = avctx->sample_rate;
+    s->config.wave.channels   = avctx->channels == 2 ? PCM_STEREO : PCM_MONO;
+    s->shine = L3_initialise(&s->config);
+    if (!s->shine)
+        return AVERROR(ENOMEM);
+    avctx->frame_size = samp_per_frame;
+    ff_af_queue_init(avctx, &s->afq);
+    return 0;
+}
+
+static int shine_encode_frame(AVCodecContext *avctx, AVPacket *avpkt,
+                              const AVFrame *frame, int *got_packet_ptr)
+{
+    SHINEContext *s = avctx->priv_data;
+    MPADecodeHeader hdr;
+    unsigned char *data;
+    long written;
+    int ret, len;
+
+    if (frame)
+        data = L3_encode_frame(s->shine, frame->data[0], &written);
+    else
+        data = L3_flush(s->shine, &written);
+    if (written < 0)
+        return -1;
+    if (written > 0) {
+        if (s->buffer_index + written > BUFFER_SIZE) {
+            av_log(avctx, AV_LOG_ERROR, "internal buffer too smalln");
+            return AVERROR_BUG;
+        }
+        memcpy(s->buffer + s->buffer_index, data, written);
+        s->buffer_index += written;
+    }
+    if (frame) {
+        if ((ret = ff_af_queue_add(&s->afq, frame)) < 0)
+            return ret;
+    }
+
+    if (s->buffer_index < 4 || !s->afq.frame_count)
+        return 0;
+    if (avpriv_mpegaudio_decode_header(&hdr, AV_RB32(s->buffer))) {
+        av_log(avctx, AV_LOG_ERROR, "free format output not supportedn");
+        return -1;
+    }
+
+    len = hdr.frame_size;
+    if (len <= s->buffer_index) {
+        if ((ret = ff_alloc_packet2(avctx, avpkt, len)))
+            return ret;
+        memcpy(avpkt->data, s->buffer, len);
+        s->buffer_index -= len;
+        memmove(s->buffer, s->buffer + len, s->buffer_index);
+
+        ff_af_queue_remove(&s->afq, avctx->frame_size, &avpkt->pts,
+                           &avpkt->duration);
+
+        avpkt->size = len;
+        *got_packet_ptr = 1;
+    }
+    return 0;
+}
+
+static av_cold int shine_encode_close(AVCodecContext *avctx)
+{
+    SHINEContext *s = avctx->priv_data;
+
+    ff_af_queue_close(&s->afq);
+    L3_close(s->shine);
+    return 0;
+}
+
+static const int libshine_sample_rates[] = {
+    44100, 48000, 32000, 0
+};
+
+AVCodec ff_libshine_encoder = {
+    .name                  = "libshine",
+    .type                  = AVMEDIA_TYPE_AUDIO,
+    .id                    = CODEC_ID_MP3,
+    .priv_data_size        = sizeof(SHINEContext),
+    .init                  = shine_encode_init,
+    .encode2               = shine_encode_frame,
+    .close                 = shine_encode_close,
+    .capabilities          = CODEC_CAP_DELAY,
+    .sample_fmts           = (const enum AVSampleFormat[]){ AV_SAMPLE_FMT_S16P,
+                                                            AV_SAMPLE_FMT_NONE },
+    .supported_samplerates = libshine_sample_rates,
+    .channel_layouts       = (const uint64_t[]) { AV_CH_LAYOUT_MONO,
+                                                  AV_CH_LAYOUT_STEREO,
+                                                  0 },
+    .long_name             = NULL_IF_CONFIG_SMALL("libshine MP3 (MPEG audio layer 3)"),
+};
diff -rupN ffmpeg-1.1.1/libavcodec/Makefile ffmpeg-1.1.1-shine/libavcodec/Makefile
--- ffmpeg-1.1.1/libavcodec/Makefile	2013-01-06 21:53:29.000000000 +0000
+++ ffmpeg-1.1.1-shine/libavcodec/Makefile	2013-02-05 13:49:09.000000000 +0000
@@ -685,6 +685,7 @@ OBJS-$(CONFIG_LIBSCHROEDINGER_DECODER)
                                              libschroedinger.o
 OBJS-$(CONFIG_LIBSCHROEDINGER_ENCODER)    += libschroedingerenc.o 
                                              libschroedinger.o
+OBJS-$(CONFIG_LIBSHINE_ENCODER)           += libshine.o
 OBJS-$(CONFIG_LIBSPEEX_DECODER)           += libspeexdec.o
 OBJS-$(CONFIG_LIBSPEEX_ENCODER)           += libspeexenc.o audio_frame_queue.o
 OBJS-$(CONFIG_LIBSTAGEFRIGHT_H264_DECODER)+= libstagefright.o
#-----set up Synology toolchain
cd ~/Downloads
export DL_PATH="http://sourceforge.net/projects/dsgpl/files/DSM%204.1%20Tool%20Chains"

#----------------------------------------------------
#-----NOW PASTE ONE OF THE FOLLOWING THREE BLOCKS TO THE TERMINAL, DEPENDING ON YOUR TARGET CPU TYPE




#-----Marvell Kirkwood mv6281/mv6282 CPU is based on the ARMv5TE core which has DSP and thumb instruction support
#-----http://www.arm.com/products/processors/technologies/dsp-simd.php
#-----http://www.marvell.com/embedded-processors/kirkwood/assets/88f6282-3_pb.pdf
wget "${DL_PATH}/Marvell%2088F628x%20Linux%202.6.32/gcc421_glibc25_88f6281-GPL.tgz"
tar xvzf gcc421_glibc25_88f6281-GPL.tgz
export CROSS_PREFIX=arm-none-linux-gnueabi
export TOOLCHAIN=/usr/local/${CROSS_PREFIX}
export TARGET=armle-unknown-linux
export MARCH=-march=armv5te
export LAME_CONFIG="./configure --prefix=${TOOLCHAIN} --host=${TARGET} --build=x86_64-linux-gnu --enable-shared --disable-static --disable-decoder"
export SSL_CONFIG="./Configure.syno --prefix=${TOOLCHAIN} threads shared linux-elf-armle"
#-----ARMv5TE CPU-specific options (based on inspecting 'configure' script https://github.com/FFmpeg/FFmpeg/blob/master/configure)
#-----pkg-config needs to be defined because with cross-prefix it assumes ${CROSS_PREFIX}-pkg-config which doesn't exist, and then librtmp won't be detected
#-----zbmv decoder won't cross compile for some reason so it is disabled: https://ffmpeg.org/trac/ffmpeg/ticket/1794
#-----something changed in FFmpeg 1.1.1's configure script and now vfp and neon optimizations have to be explicitly disabled for ARMv5TE, which doesn't have those features in any case
export FF_CONFIG="./configure --arch=arm --cpu=armv5te --enable-cross-compile --cross-prefix=${TOOLCHAIN}/bin/${CROSS_PREFIX}- --target-os=linux --prefix=${TOOLCHAIN} --enable-shared --disable-static --enable-pic --disable-ffplay --disable-ffserver --disable-vfp --disable-neon --disable-debug --enable-pthreads --enable-libshine --enable-librtmp --enable-libass --disable-encoder=zmbv --pkg-config=pkg-config --extra-version=compiled_by_patters_for_Serviio"
#-----sudo apt-get automake
#-----sudo apt-get libtool
wget https://github.com/savonet/shine/zipball/master
unzip master
cd savonet-shine-*
./bootstrap
cd ..




#-----Marvell Armada 370 CPU is based on a dual issue ARMv7 core with VFPv3-16
#-----http://www.marvell.com/embedded-processors/armada-300/assets/Marvell_ARMADA_370_SoC.pdf
#-----http://www.arm.com/products/processors/technologies/vector-floating-point.php
wget "${DL_PATH}/Marvell%20armada%20370%20Linux%203.2.30/gcc464_glibc215_hard_armada370-GPL.tgz"
tar xvzf gcc464_glibc215_hard_armada370-GPL.tgz
#-----correct some missing header file links in the toolchain 
cp /usr/local/arm-marvell-linux-gnueabi/arm-marvell-linux-gnueabi/libc/thumb2/usr/include/asm/errno.h /usr/local/arm-marvell-linux-gnueabi/arm-marvell-linux-gnueabi/libc/usr/include/asm/errno.h
cp /usr/local/arm-marvell-linux-gnueabi/arm-marvell-linux-gnueabi/libc/thumb2/usr/include/asm/ioctl.h /usr/local/arm-marvell-linux-gnueabi/arm-marvell-linux-gnueabi/libc/usr/include/asm/ioctl.h
cp /usr/local/arm-marvell-linux-gnueabi/arm-marvell-linux-gnueabi/libc/thumb2/usr/include/asm/bitsperlong.h /usr/local/arm-marvell-linux-gnueabi/arm-marvell-linux-gnueabi/libc/usr/include/asm/bitsperlong.h
export CROSS_PREFIX=arm-marvell-linux-gnueabi
export TARGET=armle-unknown-linux
export TOOLCHAIN=/usr/local/${CROSS_PREFIX}
export MARCH="-mhard-float -mfpu=vfpv3-d16"
export LAME_CONFIG="./configure --prefix=${TOOLCHAIN} --host=${TARGET} --build=x86_64-linux-gnu --enable-shared --disable-static --disable-decoder"
export SSL_CONFIG="./Configure.syno --prefix=${TOOLCHAIN} threads shared no-asm linux-elf-armle"
#-----ARMv7 CPU-specific options (based on inspecting 'configure' script https://github.com/FFmpeg/FFmpeg/blob/master/configure)
#-----pkg-config needs to be defined because with cross-prefix it assumes ${CROSS_PREFIX}-pkg-config which doesn't exist, and then librtmp won't be detected
export FF_CONFIG="./configure --arch=arm --enable-cross-compile --cross-prefix=${TOOLCHAIN}/bin/${CROSS_PREFIX}- --target-os=linux --prefix=${TOOLCHAIN} --enable-shared --disable-static --enable-pic --disable-ffplay --disable-ffserver --disable-neon --enable-thumb --disable-debug --enable-pthreads  --enable-libmp3lame --enable-librtmp --enable-libass --pkg-config=pkg-config --extra-version=compiled_by_patters_for_Serviio"




#-----Intel CPUs used in Synology products are all 64bit and SSSE3 capable but existing DSM libs are 32bit, so use the i686 toolchain rather than x86_64
wget "${DL_PATH}/Intel%20x86%20Linux%203.2.11%20%28Pineview%29/gcc421_glibc236_x86-GPL.tgz"
tar xvzf gcc421_glibc236_x86-GPL.tgz
export CROSS_PREFIX=i686-linux-gnu
export TOOLCHAIN=/usr/local/${CROSS_PREFIX}
export TARGET=i686-linux-gnu
export LAME_CONFIG="./configure --prefix=${TOOLCHAIN} --host=${TARGET} --build=x86_64-linux-gnu --enable-shared --disable-static --disable-decoder --enable-nasm"
export SSL_CONFIG="./Configure.syno --prefix=${TOOLCHAIN} threads shared linux-generic32"
#-----Intel CPU-specific options (based on inspecting 'configure' script https://github.com/FFmpeg/FFmpeg/blob/master/configure)
#-----pkg-config needs to be defined because with cross-prefix it assumes ${CROSS_PREFIX}-pkg-config which doesn't exist, and then librtmp won't be detected
export FF_CONFIG="./configure --arch=x86 --enable-ssse3 --enable-cross-compile --cross-prefix=${TOOLCHAIN}/bin/${CROSS_PREFIX}- --target-os=linux --prefix=${TOOLCHAIN} --enable-shared --disable-static --enable-pic --disable-ffplay --disable-ffserver --disable-debug --enable-pthreads --enable-libmp3lame --enable-librtmp --enable-libass --pkg-config=pkg-config --extra-version=compiled_by_patters_for_Serviio"
sudo apt-get install yasm




#-----QorIQ P1022 CPU is based on the PowerPC e500v2 core which has Signal Processing Engine which is not a classic FPU design
#-----Some QorIQ models have e500mc cores with true FPUs but these are not used in 2013 series Synology NAS
#-----http://en.wikipedia.org/wiki/QorIQ
#-----http://cache.freescale.com/files/32bit/doc/fact_sheet/QP1022FS.pdf?fpsp=1
wget "${DL_PATH}/PowerPC%20QorIQ%20Linux%202.6.32/gcc4374_eglibc2874_qoriq-GPL.tgz"
tar xvzf gcc4374_eglibc2874_qoriq-GPL.tgz
export CROSS_PREFIX=powerpc-none-linux-gnuspe
export TOOLCHAIN=/usr/local/${CROSS_PREFIX}
export TARGET=powerpc-unknown-linux
export MARCH="-mcpu=8548 -mhard-float -mfloat-gprs=double"
export LAME_CONFIG="./configure --prefix=${TOOLCHAIN} --host=${TARGET} --build=x86_64-linux-gnu --enable-shared --disable-static --disable-decoder"
export SSL_CONFIG="./Configure.syno --prefix=${TOOLCHAIN} threads shared linux-ppc"
#-----PowerPC e500v2 CPU-specific options (based on inspecting 'configure' script https://github.com/FFmpeg/FFmpeg/blob/master/configure)
#-----pkg-config needs to be defined because with cross-prefix it assumes ${CROSS_PREFIX}-pkg-config which doesn't exist, and then librtmp won't be detected
export FF_CONFIG="./configure --arch=ppc --cpu=e500v2 --enable-cross-compile --cross-prefix=${TOOLCHAIN}/bin/${CROSS_PREFIX}- --target-os=linux --prefix=${TOOLCHAIN} --enable-shared --disable-static --enable-pic --disable-ffplay --disable-ffserver --disable-debug --enable-pthreads --enable-libmp3lame --enable-librtmp --enable-libass --pkg-config=pkg-config --extra-version=compiled_by_patters_for_Serviio"




#----------------------------------------------------
#-----script continues

sudo mv ${CROSS_PREFIX} /usr/local
export AR=${TOOLCHAIN}/bin/${CROSS_PREFIX}-ar
export CC=${TOOLCHAIN}/bin/${CROSS_PREFIX}-gcc
export CXX=${TOOLCHAIN}/bin/${CROSS_PREFIX}-g++
export LD=${TOOLCHAIN}/bin/${CROSS_PREFIX}-ld
export RANLIB=${TOOLCHAIN}/bin/${CROSS_PREFIX}-ranlib
export CFLAGS="-I${TOOLCHAIN}/include -O3 ${MARCH}"
export LDFLAGS="-L${TOOLCHAIN}/lib"
export PKG_CONFIG_PATH="${TOOLCHAIN}/lib/pkgconfig"


#-----fetch Synology's DSM sources for certain libraries
wget http://dl.dropbox.com/u/1188556/DSM-source.tgz
tar xvzf DSM-source.tgz
cd DSM-source

#-----libz shared lib (toolchain only has static lib)
cd zlib-1.2
./configure --prefix=${TOOLCHAIN}
make
make install
cd ..

#-----lame shared lib
cd lame-398
$LAME_CONFIG
make
make install
cd ..

#-----shine shared lib for ARMv5
[ "${CROSS_PREFIX}" == "arm-none-linux-gnueabi" ] && mv ../savonet-shine-* .
[ "${CROSS_PREFIX}" == "arm-none-linux-gnueabi" ] && cd savonet-shine-*
[ "${CROSS_PREFIX}" == "arm-none-linux-gnueabi" ] && ./configure --prefix=${TOOLCHAIN} --host=${CROSS_PREFIX} --build=x86_64-linux-gnu --enable-shared --disable-static
[ "${CROSS_PREFIX}" == "arm-none-linux-gnueabi" ] && make
[ "${CROSS_PREFIX}" == "arm-none-linux-gnueabi" ] && make install
[ "${CROSS_PREFIX}" == "arm-none-linux-gnueabi" ] && cd ..

#-----openssl shared lib (Synology seem to have tweaked the source for each CPU)
cd openssl-1.0.x-${CROSS_PREFIX}
$SSL_CONFIG
make
make install
cd ..

#-----expat shared lib
cd expat-2.x
./configure --prefix=${TOOLCHAIN} --host=${TARGET} --build=x86_64-linux-gnu --enable-shared --disable-static
make
make install
cd ..

#-----freetype shared lib
cd freetype-2.3.7
./configure --prefix=${TOOLCHAIN} --host=${TARGET} --build=x86_64-linux-gnu --enable-shared --disable-static
make
make install
cd ..

#-----finished building DSM-bundled libs
cd ..


#-----fribidi shared lib
wget http://fribidi.org/download/fribidi-0.19.5.tar.bz2
tar xvjf fribidi-0.19.5.tar.bz2
cd fribidi-0.19.5
#-----patch fribidi source since it won't compile - some sort of page size function is no longer available
#-----error on ARMv5 and Intel: fribidi-run.c:70: error: 'PAGE_SIZE' undeclared (first use in this function)
#-----error on QorIQ: include/asm/kdump.h:41: error: expected '=', ',', ';', 'asm' or '__attribute__' before 'void'
#-----clues found here:
#-----http://armbedded.eu/node/56
#-----http://lists.debian.org/debian-glibc/2006/10/msg00169.html
#-----http://my.safaribooksonline.com/book/operating-systems-and-server-administration/linux/0596009585/advanced-file-i-o/mapping_files_into_memory
[ "${CROSS_PREFIX}" == "arm-marvell-linux-gnueabi" ] || sed -i "s|include <asm/page.h>|include <unistd.h>\n#   define PAGE_SIZE sysconf(_SC_PAGESIZE)|" lib/common.h
./configure --prefix=${TOOLCHAIN} --host=${TARGET} --build=x86_64-linux-gnu --enable-shared --disable-static
make
make install
cd ..

#-----fontconfig shared lib
wget http://freedesktop.org/software/fontconfig/release/fontconfig-2.10.91.tar.gz
tar xvzf fontconfig-2.10.91.tar.gz
cd fontconfig-2.10.91
LDFLAGS=-Wl,-rpath,\'\$\$ORIGIN\' ./configure --prefix=${TOOLCHAIN} --host=${TARGET} --build=x86_64-linux-gnu --enable-shared --disable-static
#-----correct some non-standard library linking behaviour
sed -i "s/^hardcode_into_libs=yes/hardcode_into_libs=no/" libtool
make
make install
cd ..

#-----libass shared lib
wget http://libass.googlecode.com/files/libass-0.10.1.tar.gz
tar xvzf libass-0.10.1.tar.gz
cd libass-0.10.1
LDFLAGS=-Wl,-rpath,\'\$\$ORIGIN\' ./configure --prefix=${TOOLCHAIN} --host=${TARGET} --build=x86_64-linux-gnu --enable-shared --disable-static
#-----correct some non-standard library linking behaviour
sed -i "s/^hardcode_into_libs=yes/hardcode_into_libs=no/" libtool
make
make install
cd ..

#-----librtmp shared lib
wget http://download.serviio.org/opensource/rtmpdump.tar.gz
tar xvfz rtmpdump.tar.gz
cd rtmpdump
make CROSS_COMPILE=${TOOLCHAIN}/bin/${CROSS_PREFIX}- SYS=posix prefix=${TOOLCHAIN} INC=-I${TOOLCHAIN}/include XLDFLAGS=-L${TOOLCHAIN}/lib
make install prefix=${TOOLCHAIN}
cd ..

#-----FFmpeg
wget http://www.ffmpeg.org/releases/ffmpeg-1.1.1.tar.gz
tar xvzf ffmpeg-1.1.1.tar.gz
cd ffmpeg-1.1.1

#-----patch in libshine support for ARMv5 build
[ "${CROSS_PREFIX}" == "arm-none-linux-gnueabi" ] && wget http://dl.dropbox.com/u/1188556/ffmpeg-1.1.1-shine.patch
[ "${CROSS_PREFIX}" == "arm-none-linux-gnueabi" ] && patch --verbose -p1 < ffmpeg-1.1.1-shine.patch
#-----fetch latest wrapper source code from richardpl's FFmpeg fork
[ "${CROSS_PREFIX}" == "arm-none-linux-gnueabi" ] && wget --no-check-certificate https://raw.github.com/richardpl/FFmpeg/shine/libavcodec/libshine.c -O libavcodec/libshine.c

#-----ARM-specific fixed point maths modification for mpeg audio encoder
[ "${CROSS_PREFIX}" == "arm-none-linux-gnueabi" ] && sed -i "s/^#define USE_FLOATS//" libavcodec/mpegaudioenc.c

#-----QorIQ-specific fix provided by Synology Support 
[ "${CROSS_PREFIX}" == "powerpc-none-linux-gnuspe" ] && wget http://dl.dropbox.com/u/1188556/ffmpeg-synology-qoriq.patch
[ "${CROSS_PREFIX}" == "powerpc-none-linux-gnuspe" ] && patch --verbose -p1 < ffmpeg-synology-qoriq.patch

#-----set library linker to use relative library search path (../lib and .)
#-----tricky shell escaping of $ORIGIN is required http://itee.uq.edu.au/~daniel/using_origin/
#-----http://stackoverflow.com/questions/6562403/i-dont-understand-wl-rpath-wl
#-----the use of -rpath-link appears to be necessary for compilation using the Armada370 toolchain
LDFLAGS=-Wl,-rpath,\'\$\$ORIGIN/../lib:\$\$ORIGIN\',-rpath-link,${TOOLCHAIN}/lib ${FF_CONFIG}

#-----external libs (librtmp, libass) don't seem to link correctly as shared libs
#-----on QorIQ and Intel toolchains without this fix (check with objdump -x)
[ "${CROSS_PREFIX}" == "arm-none-linux-gnueabi" ] || sed -r -i 's%(^LDFLAGS.*)\$\(LDFLAGS\)%\1-Wl,-rpath=z888z\$\$ORIGIN/../libz888z%' common.mak; sed -i "s/z888z/'/g" common.mak


make
make install
cd ..

mkdir native-${CROSS_PREFIX}
cd native-${CROSS_PREFIX}
cp ${TOOLCHAIN}/bin/ffmpeg .
cp ${TOOLCHAIN}/lib/libass.so.4 .
cp ${TOOLCHAIN}/lib/libavcodec.so.54 .
cp ${TOOLCHAIN}/lib/libavdevice.so.54 .
cp ${TOOLCHAIN}/lib/libavfilter.so.3 .
cp ${TOOLCHAIN}/lib/libavformat.so.54 .
cp ${TOOLCHAIN}/lib/libavutil.so.52 .
cp ${TOOLCHAIN}/lib/libfontconfig.so.1 .
cp ${TOOLCHAIN}/lib/libfribidi.so.0 .
cp ${TOOLCHAIN}/lib/librtmp.so.0 .
cp ${TOOLCHAIN}/lib/libswresample.so.0 .
cp ${TOOLCHAIN}/lib/libswscale.so.2 .
[ "${CROSS_PREFIX}" == "arm-none-linux-gnueabi" ] && cp ${TOOLCHAIN}/lib/libshine.so.1 .
sudo chown root:root *
cd ..
mkdir oldDSM
cd oldDSM
cp ${TOOLCHAIN}/lib/libz.so.1 .
cp ${TOOLCHAIN}/lib/libcrypto.so.1.0.0 .
cp ${TOOLCHAIN}/lib/libssl.so.1.0.0 .
cp ${TOOLCHAIN}/lib/libmp3lame.so.0 .
cp ${TOOLCHAIN}/lib/libfreetype.so.6 .
cp ${TOOLCHAIN}/lib/libexpat.so.1 .
sudo chown root:root *
cd ..
 

Native compile for ARMv5 systems

Here is the method to natively compile FFmpeg with libshine and libass, with shared libraries rather than static. It transpires that the cross compiled build has a broken AC3 encoder which produces crackling and muffled audio streams. The guide assumes a bootstrapped NAS with the optware-devel tools installed. See my much earlier blog post for details of how to set that up, since there are some pitfalls. I haven’t run through and tested the whole sequence – I have assembled this from hundreds of lines of notes I made while working – but I’m publishing it here to help others, and to refer to in future when I need to build newer FFmpeg revisions for future Serviio releases.

#-----on a bootstrapped system that's since had its DSM upgraded the path has been reset
#export PATH=/opt/bin:/opt/sbin:$PATH

#-----fetch headers for zlib, OpenSSL, LAME, freetype2, and expat libraries obtained by compiling the DSM 4.1 included versions from source, plus ncurses headers from Synology ARMv5 cross toolchain
cd /tmp
mkdir include
cd include
wget http://dl.dropbox.com/u/1188556/synology-armv5te-headers.tgz
tar xvzf synology-armv5te-headers.tgz

#-----this archive also contains pkgconfig definitions for DSM's existing versions of libcrypto, libssl and freetype2, edited to fit requirements
#-----this will allow librtmp to link to DSM's existing OpenSSL libraries, and libass to link to DSM's bundled freetype2 and expat libraries
cp *.pc /opt/lib/pkgconfig
cd /volume1/@tmp

#-----fetch savonet-shine source which has already had the bootstrap script run on a Linux system with automake
#-----on the Linux system you would need to run:
#sudo apt-get automake
#sudo apt-get libtool
#wget https://github.com/savonet/shine/zipball/master
#unzip master
#cd savonet-shine-*
#./bootstrap
#cd ..
#-----but here's one I made earlier:
wget http://dl.dropbox.com/u/1188556/savonet-shine-43c74e7.tgz
tar xvzf savonet-shine-43c74e7.tgz
cd savonet-shine-43c74e7
./configure --prefix=/opt --enable-shared --disable-static
make
#-----don't worry about the repeated message #warning NO MULT FILE FOR ARCHITECTURE - USING GENERIC MATH
#-----the include for mult_sarm_gcc.h (optimized ARM maths in assembler) is commented out in src/lib/types.h
#-----the comment mentions that fixing this is on the to-do list, so it can't be helped
make install
cd ..

#-----fetch libRTMP source
wget http://download.serviio.org/opensource/rtmpdump.tar.gz
tar xvfz rtmpdump.tar.gz
cd rtmpdump
#-----the following options will force it to use the existing OpenSSL libs thanks to the imported headers
make SYS=posix prefix=/opt INC=-I/tmp/include XLDFLAGS=-L/lib
make install prefix=/opt
cp -R /opt/include/librtmp /tmp/include
#-----remove unsupported URL line from pkgconfig/librtmp.pc
#-----pkg-config --exists --print-errors librtmp
sed -i -e '/^URL/d' /opt/lib/pkgconfig/librtmp.pc
cd ..

#-----fetch fribidi source
wget http://fribidi.org/download/fribidi-0.19.5.tar.bz2
tar xvjf fribidi-0.19.5.tar.bz2
cd fribidi-0.19.5
#-----patch fribidi source since it won't compile on ARMv5
#-----fribidi-run.c:70: error: 'PAGE_SIZE' undeclared (first use in this function)
#-----clues found here:
#-----http://armbedded.eu/node/56
#-----http://lists.debian.org/debian-glibc/2006/10/msg00169.html
#-----http://my.safaribooksonline.com/book/operating-systems-and-server-administration/linux/0596009585/advanced-file-i-o/mapping_files_into_memory
sed -i "s|include <asm/page.h>|include <unistd.h>\n#   define PAGE_SIZE sysconf(_SC_PAGESIZE)|" lib/common.h
./configure --prefix=/opt --enable-shared --disable-static
make
make install
cd ..

#-----fetch fontconfig source
wget http://freedesktop.org/software/fontconfig/release/fontconfig-2.10.91.tar.gz
tar xvzf fontconfig-2.10.91.tar.gz
cd fontconfig-2.10.91
LDFLAGS=-Wl,-rpath='$$ORIGIN' ./configure --prefix=/opt --enable-shared --disable-static
#-----correct some non-standard library linking behaviour
sed -i "s/^hardcode_into_libs=yes/hardcode_into_libs=no/" libtool
sed -i "s|^sys_lib_search_path_spec="/opt/lib/gcc/arm-none-linux-gnueabi/4.2.3 /opt/arm-none-linux-gnueabi/lib /opt/lib /lib /usr/lib"|sys_lib_search_path_spec="/lib /usr/lib /opt/lib/gcc/arm-none-linux-gnueabi/4.2.3 /opt/arm-none-linux-gnueabi/lib /opt/lib"|" libtool
make
make install
cd ..

#-----fetch libass source
wget http://libass.googlecode.com/files/libass-0.10.1.tar.gz
tar xvzf libass-0.10.1.tar.gz
cd libass-0.10.1
export CFLAGS=-I/tmp/include
LDFLAGS=-Wl,-rpath='$$ORIGIN' ./configure --prefix=/opt --enable-shared --disable-static
#-----correct some non-standard library linking behaviour
sed -i "s/^hardcode_into_libs=yes/hardcode_into_libs=no/" libtool
sed -i "s|^sys_lib_search_path_spec="/opt/lib/gcc/arm-none-linux-gnueabi/4.2.3 /opt/arm-none-linux-gnueabi/lib /opt/lib /lib /usr/lib"|sys_lib_search_path_spec="/lib /usr/lib /opt/lib/gcc/arm-none-linux-gnueabi/4.2.3 /opt/arm-none-linux-gnueabi/lib /opt/lib"|" libtool
make
make install
cd ..

#-----fetch exact FFmpeg source version that Serviio requires
wget http://www.ffmpeg.org/releases/ffmpeg-1.1.1.tar.gz
tar xvzf ffmpeg-1.1.1.tar.gz
cd ffmpeg-1.1.1

#-----libshine patch by richardpl posted in FFmpeg ticket which I raised, applied to ffmpeg-1.1.1 by hand, then diffed to make patch file
#-----https://ffmpeg.org/trac/ffmpeg/ticket/1457
wget http://dl.dropbox.com/u/1188556/ffmpeg-1.1.1-shine.patch
patch -p1 --verbose < ffmpeg-1.1.1-shine.patch
#-----fetch latest wrapper source code from richardpl's FFmpeg fork
wget --no-check-certificate https://raw.github.com/richardpl/FFmpeg/shine/libavcodec/libshine.c -O libavcodec/libshine.c

#-----ARMv5-specific fixed point maths modification for mpeg audio encoder, mentioned in same ticket
sed -i "s/^#define USE_FLOATS//" libavcodec/mpegaudioenc.c

#-----FFmpeg configure parameters for native compile with shared libraries
#-----something changed in FFmpeg 1.1.1's configure script and now vfp and neon optimizations have to be explicitly disabled for ARMv5TE, which doesn't have those features in any case
export FF_CONFIG="./configure --arch=arm --cpu=armv5te --prefix=/opt --extra-cflags=-I/tmp/include --enable-shared --disable-static --enable-pic --disable-ffplay --disable-ffserver --disable-vfp --disable-neon --disable-debug --enable-pthreads --enable-libshine --enable-librtmp --enable-libass --extra-version=compiled_by_patters_for_Serviio"

#-----set library linker to use relative library search path (../lib:.)
#-----tricky shell escaping of $ORIGIN is required http://itee.uq.edu.au/~daniel/using_origin/
#-----http://stackoverflow.com/questions/6562403/i-dont-understand-wl-rpath-wl
LDFLAGS=-Wl,-rpath='$$ORIGIN/../lib:$$ORIGIN' ${FF_CONFIG}
make
make install

mkdir /volume1/@tmp/binaries
cd /volume1/@tmp/binaries
mkdir bin
mkdir lib
cp /opt/bin/ffmpeg bin
cp /opt/lib/libass.so.4 lib
cp /opt/lib/libavcodec.so.54 lib
cp /opt/lib/libavdevice.so.54 lib
cp /opt/lib/libavfilter.so.3 lib
cp /opt/lib/libavformat.so.54 lib
cp /opt/lib/libavutil.so.52 lib
cp /opt/lib/libfontconfig.so.1 lib
cp /opt/lib/libfribidi.so.0 lib
cp /opt/lib/librtmp.so.0 lib
cp /opt/lib/libshine.so.1 lib
cp /opt/lib/libswresample.so.0 lib
cp /opt/lib/libswscale.so.2 lib
tar -cvf ffmpeg-syno-armv5tel.tar *
gzip ffmpeg-syno-armv5tel.tar
mv ffmpeg-syno-armv5tel.tar.gz ffmpeg-syno-armv5tel.tgz
 

Here’s how the library dependencies look. There are no links to any libraries in /opt, and DSM-bundled libraries in /lib are linked to where possible. All the other new libraries are kept within the Serviio lib folder (../lib relative to the ffmpeg binary).

~$ ldd /volume1/@appstore/Serviio/bin/ffmpeg
        libavdevice.so.54 => /volume1/@appstore/Serviio/bin/../lib/libavdevice.so.54 (0x40026000)
        libavfilter.so.3 => /volume1/@appstore/Serviio/bin/../lib/libavfilter.so.3 (0x40038000)
        libavformat.so.54 => /volume1/@appstore/Serviio/bin/../lib/libavformat.so.54 (0x40095000)
        libavcodec.so.54 => /volume1/@appstore/Serviio/bin/../lib/libavcodec.so.54 (0x401db000)
        libswresample.so.0 => /volume1/@appstore/Serviio/bin/../lib/libswresample.so.0 (0x40f36000)
        libswscale.so.2 => /volume1/@appstore/Serviio/bin/../lib/libswscale.so.2 (0x40f50000)
        libavutil.so.52 => /volume1/@appstore/Serviio/bin/../lib/libavutil.so.52 (0x40f90000)
        libm.so.6 => /lib/libm.so.6 (0x40fcc000)
        libpthread.so.0 => /lib/libpthread.so.0 (0x4107b000)
        libc.so.6 => /lib/libc.so.6 (0x4109b000)
        librt.so.1 => /lib/librt.so.1 (0x411ca000)
        libass.so.4 => /volume1/@appstore/Serviio/bin/../lib/libass.so.4 (0x411d9000)
        librtmp.so.0 => /volume1/@appstore/Serviio/bin/../lib/librtmp.so.0 (0x4120b000)
        libz.so.1 => /lib/libz.so.1 (0x4122a000)
        libshine.so.1 => /volume1/@appstore/Serviio/bin/../lib/libshine.so.1 (0x41247000)
        /lib/ld-linux.so.3 (0x40000000)
        libfreetype.so.6 => /lib/libfreetype.so.6 (0x41259000)
        libfribidi.so.0 => /volume1/@appstore/Serviio/bin/../lib/libfribidi.so.0 (0x41318000)
        libfontconfig.so.1 => /volume1/@appstore/Serviio/bin/../lib/libfontconfig.so.1 (0x41337000)
        libssl.so.1.0.0 => /lib/libssl.so.1.0.0 (0x41386000)
        libcrypto.so.1.0.0 => /lib/libcrypto.so.1.0.0 (0x413d2000)
        libgcc_s.so.1 => /lib/libgcc_s.so.1 (0x4156d000)
        libexpat.so.1 => /lib/libexpat.so.1 (0x41581000)
        libdl.so.2 => /lib/libdl.so.2 (0x415a8000)

Bliss album art manager package for Synology NAS

bliss-UI

 

Bliss is a Java application written by Dan Gravell which can manage file names, tags and album art for your music collection and which can also enforce their consistency. It is designed to be left running once installed so that albums you add later will have these same policies applied to them automatically. It supports a wide range of music formats, and effortlessly deals with very large collections – apparently even ones containing quite obscure recordings. My own collection didn’t really put this to the test, since it doesn’t contain bootlegs, live sets or rarities plus I had also already obtained cover art for it (from back when CoverFlow first graced the screen of my iPhone 2G).

I could see from referrals to this blog that people were asking Dan for a Synology package which he didn’t have the time to investigate, and so I thought that it would make an interesting little project, especially since a NAS is the ideal device to run Bliss on. Although there was already a Howto post on the Synology forums, that guide only really covered getting the basic functionality of Bliss up and running – it was missing the best bits: the filesystem watching and audio fingerprinting features. These depend on natively compiled binaries, but Bliss doesn’t support running on ARM or PowerPC CPUs, and its Intel binaries don’t work on Synology either (the usual problem – glibc too old). Getting these working provided precisely the sort of challenge I like. Not only were they difficult to compile, but getting them integrated into the various OSGi ‘bundles’ that make up Bliss was quite involved too.

Bliss uses an open source library called Chromaprint, itself part of the wider Acoustid project. The aim is to scan an audio file, to produce a fingerprint of the sound, and then to compare this against an open online database such as MusicBrainz.org to identify music regardless of compression codec used. Its author Lukáš Lalinský explains how it works. It turns out that Chromaprint uses some complex maths functions that FFmpeg can provide (specifically Fourier Transform), and FFmpeg’s shared libraries are already included with Synology DSM. I was able to download the sources that were used to build DSM, which then allowed me to build Chromaprint linked to those existing libraries – resulting in a minuscule 84KB build of fpcalc, rather than the statically compiled ones for various OS and CPU architectures included with Bliss, which weigh in at several megabytes. I think I’m finally ‘getting’ what open source is all about, which is nice since that was my objective in experimenting with my NAS. To prevent fpcalc building and linking to its dynamic library libchromaprint.so and to get it to detect FFmpeg properly I had to carefully inspect the Makefiles to find the correct build syntax:
FFMPEG_DIR=/usr/local/i686-linux-gnu cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_EXAMPLES=ON -DBUILD_SHARED_LIBS=NO .
FFMPEG_DIR is the base folder from which CMake will look for lib/libavcodec.so and include/libavcodec/avfft.h.

It’s a bit of a nuisance that on ARM you first have to compile CMake because it doesn’t exist on the Optware ipkg repository, but it was straightforward enough to build. To build the Intel binaries I cross-compiled on a Ubuntu Desktop 12 virtual machine using the Synology toolchain (see the Synology DSM 3rd party developer guide for details).

For watching the filesystem Bliss uses JNotify to hook into the Linux kernel’s inotify subsystem. Getting this compiled was tricky. It seems no one has reported successfully compiling it for an ARM CPU, and JNotify’s author Omry Yadan wasn’t aware of anyone doing this either. The problem is that compilation halts with these errors:

In file included from ../net_contentobjects_jnotify_linux_JNotify_linux.c:43:
../inotify-syscalls.h:35:1: error: "__NR_inotify_init" redefined
In file included from /opt/include/sys/syscall.h:25,
                 from ../inotify-syscalls.h:4,
                 from ../net_contentobjects_jnotify_linux_JNotify_linux.c:43:
/opt/include/asm/unistd.h:344:1: error: this is the location of the previous definition
In file included from ../net_contentobjects_jnotify_linux_JNotify_linux.c:43:
../inotify-syscalls.h:36:1: error: "__NR_inotify_add_watch" redefined
In file included from /opt/include/sys/syscall.h:25,
                 from ../inotify-syscalls.h:4,
                 from ../net_contentobjects_jnotify_linux_JNotify_linux.c:43:
/opt/include/asm/unistd.h:345:1: error: this is the location of the previous definition
In file included from ../net_contentobjects_jnotify_linux_JNotify_linux.c:43:
../inotify-syscalls.h:37:1: error: "__NR_inotify_rm_watch" redefined
In file included from /opt/include/sys/syscall.h:25,
                 from ../inotify-syscalls.h:4,
                 from ../net_contentobjects_jnotify_linux_JNotify_linux.c:43:
/opt/include/asm/unistd.h:346:1: error: this is the location of the previous definition

By searching in a very generic way for a solution I found this post on stackoverflow.com which helped me to patch the header inotify-syscalls.h to get around the problem. Since there is no JDK for ARM embedded systems I included the headers from OpenJDK6 which I took from a Ubuntu VM.

Compiling on Intel also required a fix. I was getting the same error as featured in this post on the JNotify user forum on SourceForge.net:
expected specifier-qualifier-list before ‘pid_t’

Despite some people’s apparent success simply rearranging the order of the includes in net_contentobjects_jnotify_linux_JNotify_linux.c this didn’t help me. I’m not sure quite how I stumbled upon it, but I found the solution staring at me on this page:
The size_t, ssize_t, uid_t, gid_t, off_t and pid_t types are defined as described in sys/types.h

I inserted an additional include in for sys/types.h and it compiled ok. It’s worth pointing out that, although all Intel Synology units are x86-64 architecture, the Oracle JRE for Embedded is x86 (32 bit), so I used the i686 toolchain. Synology DSM’s FFmpeg shared libraries are also 32 bit so my build of fpcalc needed to comply with this. Bliss nonetheless expects the binary to be called fpcalc_linux64 since Bliss is detecting the underlying Linux CPU architecture.

Once I had got past the obstacle of compiling the native code, I needed to liaise back and forth with Dan to understand how Bliss was dealing with its libraries and how I could replace the built-in versions. Originally this was quite a kludge but Dan has since abstracted out the native binaries into their own OSGI bundle fragments which makes things a lot easier, and should allow Bliss to survive an in-app update. The Synology package provides the following architecture-specific jar files (with corresponding edits to their manifest). Thank you to Dan for all the quick answers!

  • net.contentobjects.jnotify.linux.x86-0.94.0.jar
  • net.contentobjects.jnotify.linux.ARM_le-0.94.0.jar
  • net.contentobjects.jnotify.linux.PowerPC-0.94.0.jar
  • com.elsten.bliss.policy.tag.auto.linux.x86-1.0.0.jar – contains fpcalc.
  • com.elsten.bliss.policy.tag.auto.linux.ARM_le-1.0.0.jar
  • com.elsten.bliss.policy.tag.auto.linux.PowerPC-1.0.0.jar

Here are my fixes to JNotify to get the native library part compiled, since they may help someone else:

 

Patch for native compile of JNotify 0.94 on ARM Synology

diff -crB jnotify-vanilla/inotify-syscalls.h jnotify-arm/inotify-syscalls.h
*** jnotify-vanilla/inotify-syscalls.h	2005-11-30 15:07:56.000000000 +0000
--- jnotify-arm/inotify-syscalls.h	2012-09-14 02:43:44.032130098 +0100
***************
*** 32,40 ****
  # define __NR_inotify_add_watch	152
  # define __NR_inotify_rm_watch	156
  #elif defined (__arm__)
! # define __NR_inotify_init	316
! # define __NR_inotify_add_watch	317
! # define __NR_inotify_rm_watch	318
  #elif defined (__SH4__)
  # define __NR_inotify_init	290
  # define __NR_inotify_add_watch	291
--- 32,46 ----
  # define __NR_inotify_add_watch	152
  # define __NR_inotify_rm_watch	156
  #elif defined (__arm__)
! # ifndef __NR_inotify_init
! #  define __NR_inotify_init     316
! # endif
! # ifndef __NR_inotify_add_watch
! #  define __NR_inotify_add_watch 317
! # endif
! # ifndef __NR_inotify_rm_watch
! #  define __NR_inotify_rm_watch 318
! # endif
  #elif defined (__SH4__)
  # define __NR_inotify_init	290
  # define __NR_inotify_add_watch	291
diff -crB jnotify-vanilla/Release/subdir.mk jnotify-arm/Release/subdir.mk
*** jnotify-vanilla/Release/subdir.mk	2011-02-28 18:07:20.000000000 +0000
--- jnotify-arm/Release/subdir.mk	2012-09-14 02:29:00.000000000 +0100
***************
*** 17,23 ****
  %.o: ../%.c
  	@echo 'Building file: $<'
  	@echo 'Invoking: GCC C Compiler'
! 	gcc -I/usr/lib/jvm/java-6-sun/include -I/usr/lib/jvm/java-6-sun/include/linux -O3 -Wall -Werror -c -fmessage-length=0 -fPIC -MMD -MP -MF"$(@:%.o=%.d)" -MT"$(@:%.o=%.d)" -o"$@" "$<"
  	@echo 'Finished building: $<'
  	@echo ' '
  
--- 17,23 ----
  %.o: ../%.c
  	@echo 'Building file: $<'
  	@echo 'Invoking: GCC C Compiler'
! 	gcc -I/volume1/public/temp/jdk_include/ -I/volume1/public/temp/jdk_include/linux -O3 -Wall -Werror -c -fmessage-length=0 -fPIC -MMD -MP -MF"$(@:%.o=%.d)" -MT"$(@:%.o=%.d)" -o"$@" "$<"
  	@echo 'Finished building: $<'
  	@echo ' '
 

Patch for cross compile of JNotify 0.94 for Intel Synology, using Ubuntu Desktop 12

diff -crB jnotify-vanilla/net_contentobjects_jnotify_linux_JNotify_linux.c jnotify-i686/net_contentobjects_jnotify_linux_JNotify_linux.c
*** jnotify-vanilla/net_contentobjects_jnotify_linux_JNotify_linux.c	2011-02-28 18:07:20.000000000 +0000
--- jnotify-i686/net_contentobjects_jnotify_linux_JNotify_linux.c	2012-09-14 00:41:53.455010206 +0100
***************
*** 36,41 ****
--- 36,42 ----
  #include <sys/time.h>
  #include <sys/select.h>
  #include <sys/ioctl.h>
+ #include <sys/types.h>
  #include <errno.h>
  #include <stdio.h>
  #include <unistd.h>
diff -crB jnotify-vanilla/Release/makefile jnotify-i686/Release/makefile
*** jnotify-vanilla/Release/makefile	2011-02-28 18:07:20.000000000 +0000
--- jnotify-i686/Release/makefile	2012-09-14 00:37:56.475007855 +0100
***************
*** 28,34 ****
  libjnotify.so: $(OBJS) $(USER_OBJS)
  	@echo 'Building target: $@'
  	@echo 'Invoking: GCC C Linker'
! 	gcc -shared -o"libjnotify.so" $(OBJS) $(USER_OBJS) $(LIBS)
  	@echo 'Finished building target: $@'
  	@echo ' '
  
--- 28,34 ----
  libjnotify.so: $(OBJS) $(USER_OBJS)
  	@echo 'Building target: $@'
  	@echo 'Invoking: GCC C Linker'
! 	i686-linux-gnu-gcc -shared -o"libjnotify.so" $(OBJS) $(USER_OBJS) $(LIBS)
  	@echo 'Finished building target: $@'
  	@echo ' '
  
diff -crB jnotify-vanilla/Release/subdir.mk jnotify-i686/Release/subdir.mk
*** jnotify-vanilla/Release/subdir.mk	2011-02-28 18:07:20.000000000 +0000
--- jnotify-i686/Release/subdir.mk	2012-09-14 00:37:33.835007731 +0100
***************
*** 17,23 ****
  %.o: ../%.c
  	@echo 'Building file: $<'
  	@echo 'Invoking: GCC C Compiler'
! 	gcc -I/usr/lib/jvm/java-6-sun/include -I/usr/lib/jvm/java-6-sun/include/linux -O3 -Wall -Werror -c -fmessage-length=0 -fPIC -MMD -MP -MF"$(@:%.o=%.d)" -MT"$(@:%.o=%.d)" -o"$@" "$<"
  	@echo 'Finished building: $<'
  	@echo ' '
  
--- 17,23 ----
  %.o: ../%.c
  	@echo 'Building file: $<'
  	@echo 'Invoking: GCC C Compiler'
! 	i686-linux-gnu-gcc -I/usr/local/i686-linux-gnu/include/ -I/usr/lib/jvm/java-6-openjdk-amd64/include/ -I/usr/lib/jvm/java-6-openjdk-amd64/include/linux -O3 -Wall -Werror -c -fmessage-length=0 -fPIC -MMD -MP -MF"$(@:%.o=%.d)" -MT"$(@:%.o=%.d)" -o"$@" "$<"
  	@echo 'Finished building: $<'
  	@echo ' '
 
 

Package instructions

The package can be installed on an unmodified NAS – no hacking or bootstrapping is required. Only Marvell Kirkwood, Intel, and Freescale QorIQ PowerPC CPUs are supported, so please check which CPU your NAS has.

  • Firstly, in the DSM User control panel enable the User Home service.
  • Open the DSM Package Center. In Settings -> Package Sources add my package repository URL which is http://packages.pcloadletter.co.uk
  • Install either one of my Java SE for Embedded Synology packages (Java 6 or Java 7). Only Marvell Kirkwood, Freescale QorIQ, or Intel CPUs are supported. Please read all the notes and instructions on that page carefully.
  • Install Bliss using DSM’s Package Center.
  • The rest is self-explanatory.
 

Package scripts

For information, here are the package scripts so you can see what it’s going to do. You can get more information about how packages work by reading the Synology Package wiki.

installer.sh

#!/bin/sh

#--------BLISS installer script
#--------package maintained at pcloadletter.co.uk

DOWNLOAD_FILE="latest-linux-version"
DOWNLOAD_PATH="http://www.blisshq.com/app"
DOWNLOAD_URL="${DOWNLOAD_PATH}/${DOWNLOAD_FILE}"
DAEMON_USER="`echo ${SYNOPKG_PKGNAME} | awk {'print tolower($_)'}`"
DAEMON_PASS="`openssl rand 12 -base64 2>nul`"
DAEMON_ID="${SYNOPKG_PKGNAME} daemon user"
ENGINE_SCRIPT="bliss.sh"
MIGRATION_FOLDER="${DAEMON_USER}_data_mig"
SYNO_CPU_ARCH="`uname -m`"
NATIVE_BINS_URL="http://packages.pcloadletter.co.uk/downloads/bliss-native-${SYNO_CPU_ARCH}.tgz"   
NATIVE_BINS_FILE="`echo ${NATIVE_BINS_URL} | sed -r "s%^.*/(.*)%\1%"`"
INSTALL_FILES="${DOWNLOAD_URL}"
source /etc/profile

TEMP_FOLDER="`find / -maxdepth 2 -name '@tmp' | head -n 1`"
APP_TEMP="${TEMP_FOLDER}/${SYNOPKG_PKGNAME}"
DAEMON_HOME="`cat /etc/passwd | grep "${DAEMON_ID}" | cut -f6 -d':'`"
PRIMARY_VOLUME="/`echo $TEMP_FOLDER | cut -f2 -d'/'`"


preinst ()
{
  if [ -z ${JAVA_HOME} ]; then
    echo "Java is not installed or not properly configured. JAVA_HOME is not defined. "
    echo "Download and install the Java Synology package from http://wp.me/pVshC-z5"
    exit 1
  fi
  
  if [ ! -f ${JAVA_HOME}/bin/java ]; then
    echo "Java is not installed or not properly configured. The Java binary could not be located. "
    echo "Download and install the Java Synology package from http://wp.me/pVshC-z5"
    exit 1
  fi
  
  #is the User Home service enabled?
  UH_SERVICE=maybe
  synouser --add userhometest Testing123 "User Home test user" 0 "" ""
  UHT_HOMEDIR=`cat /etc/passwd | sed -r '/User Home test user/!d;s/^.*:User Home test user:(.*):.*$/\1/'`
  if echo $UHT_HOMEDIR | grep '/var/services/homes/' > /dev/null; then
    if [ ! -d $UHT_HOMEDIR ]; then
      UH_SERVICE=false
    fi
  fi
  synouser --del userhometest
  #remove home directory (needed since DSM 4.1)
  [ -e /var/services/homes/userhometest ] && rm -r /var/services/homes/userhometest
  if [ ${UH_SERVICE} == "false" ]; then
    echo "The User Home service is not enabled. Please enable this feature in the User control panel in DSM."
    exit 1
  fi
  #fetch the URL for the latest installer version from blisshq.com  
  _download
  DOWNLOAD_URL=`cat ${TEMP_FOLDER}/${DOWNLOAD_FILE}`
  INSTALL_FILES="${DOWNLOAD_URL} ${NATIVE_BINS_URL}"
  #now fetch the installer
  _download
  exit 0
}

_download ()
{
  cd ${TEMP_FOLDER}
  for WGET_URL in ${INSTALL_FILES}
  do
    WGET_FILENAME="`echo ${WGET_URL} | sed -r "s%^.*/(.*)%\1%"`"
    [ -f ${TEMP_FOLDER}/${WGET_FILENAME} ] && rm ${TEMP_FOLDER}/${WGET_FILENAME}
    wget ${WGET_URL}
    if [[ $? != 0 ]]; then
      if [ -d ${PUBLIC_FOLDER} ] && [ -f ${PUBLIC_FOLDER}/${DOWNLOAD_FILE} ]; then
        cp ${PUBLIC_FOLDER}/${DOWNLOAD_FILE} ${TEMP_FOLDER}
      else     
        echo "There was a problem downloading ${WGET_FILENAME} from the official download link, "
        echo "which was \"${WGET_URL}\" "
        echo "Alternatively, you may download this file manually and place it in the 'public' shared folder. "
        exit 1
      fi
    fi
  done
}


postinst ()
{
  #create daemon user
  synouser --add ${DAEMON_USER} ${DAEMON_PASS} "${DAEMON_ID}" 0 "" ""
  
  #determine the daemon user homedir and save that variable in the user's profile
  #this is needed because new users seem to inherit a HOME value of /root which they have no permissions for
  DAEMON_HOME="`cat /etc/passwd | grep "${DAEMON_ID}" | cut -f6 -d':'`"
  su - ${DAEMON_USER} -s /bin/sh -c "echo export HOME=\'${DAEMON_HOME}\' >> .profile"

  #set working folder (/tmp is very small on Synology)
  #and reduce Java prefs system polling frequency to allow hibernation
  su - ${DAEMON_USER} -s /bin/sh -c "echo export VMARGS=\'-Djava.io.tmpdir=${APP_TEMP} -Djava.util.prefs.syncInterval=86400\' >> .profile"
  
  #once again fetch the URL for the latest installer version from blisshq.com
  #and use it to determine the filename we downloaded during preinst
  _download
  DOWNLOAD_FILE=`cat ${TEMP_FOLDER}/${DOWNLOAD_FILE} | sed -r "s%^.*/(.*)%\1%"`

  #run the installer
  cd ${TEMP_FOLDER}
  echo INSTALL_PATH=${SYNOPKG_PKGDEST} > ${TEMP_FOLDER}/bliss-synology.properties
  java -jar ${TEMP_FOLDER}/${DOWNLOAD_FILE} -options ${TEMP_FOLDER}/bliss-synology.properties
  rm ${TEMP_FOLDER}/bliss-synology.properties
  rm ${TEMP_FOLDER}/${DOWNLOAD_FILE}
  sed -i "s%^#!/bin/bash%#!/bin/sh%" ${SYNOPKG_PKGDEST}/bin/${ENGINE_SCRIPT}
  
  #stow jar files containing Synology versions of native code
  #(libjnotify, and fpcalc from the Chromaprint audio fingerprinting library)
  mkdir ${SYNOPKG_PKGDEST}/syno-native
  cd ${SYNOPKG_PKGDEST}/syno-native
  tar xzf ${TEMP_FOLDER}/${NATIVE_BINS_FILE}

  #change owner of folder tree and temp files
  chown -R ${DAEMON_USER} ${SYNOPKG_PKGDEST}
  chown -R ${DAEMON_USER} ${DAEMON_HOME} 
  [ -d ${APP_TEMP} ] && chown -R ${DAEMON_USER} ${APP_TEMP}

  exit 0
}


preuninst ()
{
  #make sure daemon is stopped
  /var/packages/${SYNOPKG_PKGNAME}/scripts/start-stop-status stop
  sleep 6
  
  exit 0
}


postuninst ()
{
  #remove daemon user
  synouser --del ${DAEMON_USER}
  
  #clean up temp
  [ -d ${TEMP_FOLDER}/Bliss ] && rm -r ${TEMP_FOLDER}/Bliss

  exit 0
}


preupgrade ()
{
  #make sure daemon is stopped
  /var/packages/${SYNOPKG_PKGNAME}/scripts/start-stop-status stop
  sleep 6
  
  #if config data exists back it up
  if [ -d ${DAEMON_HOME}/.bliss ]; then
    mkdir ${SYNOPKG_PKGDEST}/../${DAEMON_USER}_data_migration
    mv ${DAEMON_HOME}/.bliss ${SYNOPKG_PKGDEST}/../${DAEMON_USER}_data_migration
    mv ${DAEMON_HOME}/.java ${SYNOPKG_PKGDEST}/../${DAEMON_USER}_data_migration
  fi

  exit 0
}


postupgrade ()
{
  #use the migrated config data from the previous version
  if [ -d ${SYNOPKG_PKGDEST}/../${DAEMON_USER}_data_migration/.bliss ]; then
    mv ${SYNOPKG_PKGDEST}/../${DAEMON_USER}_data_migration/.bliss ${DAEMON_HOME}
    mv ${SYNOPKG_PKGDEST}/../${DAEMON_USER}_data_migration/.java ${DAEMON_HOME}
    rmdir ${SYNOPKG_PKGDEST}/../${DAEMON_USER}_data_migration
    
    #daemon user has been deleted and recreated so we need to reset ownership (new UID)
    chown -R ${DAEMON_USER} ${DAEMON_HOME}
  fi
  
  exit 0
}
 

start-stop-status.sh

#!/bin/sh

#--------BLISS start-stop-status script
#--------package maintained at pcloadletter.co.uk

DAEMON_USER="`echo ${SYNOPKG_PKGNAME} | awk {'print tolower($_)'}`"
DAEMON_ID="${SYNOPKG_PKGNAME} daemon user"
ENGINE_SCRIPT="bliss.sh"
PIDFILE="${SYNOPKG_PKGDEST}/${SYNOPKG_PKGNAME}.pid"
SYNO_CPU_ARCH="`uname -m`"

_findpid ()
{
  ps -w | grep "^ *[0-9]* ${DAEMON_USER} .*java.*-Djava\.io\.tmpdir=/volume1/@tmp/Bliss" | grep -v "grep" | awk '{ print $1 }'
}


case $1 in
  start)
    PID=`_findpid`
    if [ -n "$PID" ]; then
      echo daemon is already running with pid $PID
      exit 1;
    fi
    DAEMON_HOME="`cat /etc/passwd | grep "${DAEMON_ID}" | cut -f6 -d':'`"
    
    #set the current timezone for Java so that log timestamps are accurate
    #we need to use the modern timezone names so that Java can figure out DST
    SYNO_TZ=`cat /etc/synoinfo.conf | grep timezone | cut -f2 -d'"'`
    SYNO_TZ=`grep "^${SYNO_TZ}" /usr/share/zoneinfo/Timezone/tzname | sed -e "s/^.*= //"`
    grep "^export TZ" ${DAEMON_HOME}/.profile > /dev/null \
     && sed -i "s%^export TZ=.*$%export TZ='${SYNO_TZ}'%" ${DAEMON_HOME}/.profile \
     || echo export TZ=\'${SYNO_TZ}\' >> ${DAEMON_HOME}/.profile
    
    #update the package version number in case of an in-app update
    #find must write out to a file because vars cannot be defined in subshells
    #http://mywiki.wooledge.org/BashFAQ/024
    find ${SYNOPKG_PKGDEST}/felix-cache -name "*.info" > /tmp/bliss-v-check.txt
    while IFS="" read -r FILE_TO_PARSE; do
      if [ -e ${FILE_TO_PARSE} ]; then
        if grep "com.elsten.bliss.bundle" ${FILE_TO_PARSE} > /dev/null; then
          BLISS_BUNDLE_DIR="`dirname ${FILE_TO_PARSE}`"
        fi
      fi
    done < /tmp/bliss-v-check.txt
    #read current version
    BLISS_VERSION=`grep "version" /var/packages/${SYNOPKG_PKGNAME}/INFO | sed -r "s/^.*([0-9]{8}).*$/\1/"`
    if [ -d ${BLISS_BUNDLE_DIR} ]; then
      find ${BLISS_BUNDLE_DIR} -name *.jar > /tmp/bliss-v-check.txt
      while IFS="" read -r FILE_TO_PARSE; do
        if [ -e ${FILE_TO_PARSE} ]; then
          DETECTED_VERSION=`unzip -p ${FILE_TO_PARSE} META-INF/MANIFEST.MF | grep Bundle-Version | cut -f4 -d'.' |  cut -c1-8`
        fi
        if [ ${DETECTED_VERSION} -gt ${BLISS_VERSION} ]; then
          BLISS_VERSION=${DETECTED_VERSION}
        fi
      done < /tmp/bliss-v-check.txt
    fi
    rm /tmp/bliss-v-check.txt
    sed -r -i "s/^version=\"[0-9]{8}/version=\"${BLISS_VERSION}/" /var/packages/Bliss/INFO
    
    #update the CPU-specific repository customizations (in case of an in-app update)
    if [ ${SYNO_CPU_ARCH} == "armv5tel" ]; then
      sed -i "s/jnotify\.linux\.x86/jnotify\.linux\.ARM_le/g" ${SYNOPKG_PKGDEST}/bliss-bundle/repository.xml
      sed -i "s/policy\.tag\.auto\.linux\.x86/policy\.tag\.auto\.linux\.ARM_le/g" ${SYNOPKG_PKGDEST}/bliss-bundle/repository.xml
    fi
    if [ ${SYNO_CPU_ARCH} == "ppc" ]; then
      sed -i "s/jnotify\.linux\.x86/jnotify\.linux\.PowerPC/g" ${SYNOPKG_PKGDEST}/bliss-bundle/repository.xml
      sed -i "s/policy\.tag\.auto\.linux\.x86/policy\.tag\.auto\.linux\.PowerPC/g" ${SYNOPKG_PKGDEST}/bliss-bundle/repository.xml
    fi
    
    #overwrite native lib bundles with syno versions (in case of an in-app update)
    cp ${SYNOPKG_PKGDEST}/syno-native/* ${SYNOPKG_PKGDEST}/bliss-bundle

    #start daemon in background mode
    echo "Starting ${SYNOPKG_PKGNAME} ... "
    su - ${DAEMON_USER} -s /bin/sh -c "cd ${SYNOPKG_PKGDEST}/bin && ./${ENGINE_SCRIPT} > ${SYNOPKG_PKGDEST}/bliss.out 2>&1 &"
    sleep 2
    PID=`_findpid`
    if [ -n "$PID" ]; then
      echo $PID > $PIDFILE
      echo "OK"
    else
      echo "FAIL"
      exit 1
    fi

    #set up symlink for the DSM GUI
    if [ -d /usr/syno/synoman/webman/3rdparty ]; then
      ln -s ${SYNOPKG_PKGDEST}/DSM/${SYNOPKG_PKGNAME} /usr/syno/synoman/webman/3rdparty/${SYNOPKG_PKGNAME}
    fi
   
    exit 0
  ;;

  stop)
    echo -n "Stopping daemon ... "
    if [ -f $PIDFILE ] ; then
      kill `cat $PIDFILE`
      sleep 10
    fi
    PID=`_findpid`
    if [ -n "$PID" ]; then
      echo "Still running, killing PID=$PID ... "
      kill -9 $PID
    fi
    rm -f $PIDFILE
    echo "OK"
    
    #remove DSM icon symlink
    if [ -e /usr/syno/synoman/webman/3rdparty/${SYNOPKG_PKGNAME} ]; then
      rm /usr/syno/synoman/webman/3rdparty/${SYNOPKG_PKGNAME}
    fi

    exit 0
  ;;

  status)
    PID=`_findpid`
    if [ -n "$PID" ]; then
      exit 0
    else
      exit 1
    fi
  ;;

  log)
    echo "${SYNOPKG_PKGDEST}/bliss.out"
    exit 0
  ;;
esac
 

Changelog:

  • 20130213-0009 Updated to Bliss 20130213, and will correctly report version in Package Center after an in-app update
  • 20130131-0008 Updated to Bliss 20130131
  • 20121112-0007 Fixes for DSM 4.2
  • 20121112-006 Updated to Bliss 20121112
  • 20121019-005 Updated to Bliss 20121019
  • 20121002-004 Updated to Bliss 20121002
  • 20120830-003 Added support for Freescale QorIQ PowerPC CPUs used in some Synology x13 series products, PowerPC processors in previous Synology generations with older glibc versions are not supported
  • 20120830-002 Hopefully fixed Java prefs polling issue that prevented NAS hibernation
  • 20120830-001 initial public release

 
 

Unified Windows PE 4.0 builder for Windows ADK

This script will build Windows PE 4.0 (for x86, or AMD64 or both) including scripts and drivers of your choosing, it will create ISO images with both BIOS and UEFI support, and will also upload the resulting WIM boot images to your WDS server automatically (and freshen them if they have been re-created). This reduces the tiresome task of boot image maintenance to just a couple of clicks.

It uses only the standard Microsoft Windows ADK tools, which is the new name for WAIK. Just save the code below as Build_WinPE.cmd and right-click on it to Run as Administrator. Notice the defined variables at the start, particularly the %SOURCE% folder. It supports using either the 32bit or the 64bit ADK, and only the Windows PE and Deployment Tools ADK components are required. The script expects the following folders:

  • %SOURCE%\scripts\WinPE – any additional scripts (e.g. OS build scripts)
  • %SOURCE%\drivers\WinPE-x86\CURRENT – drivers
  • %SOURCE%\drivers\WinPE-AMD64\CURRENT
  • %SOURCE%\tools\WinPE-x86 – optional tools such as GImageX, or apps from portableapps.com
  • %SOURCE%\tools\WinPE-AMD64

Notice the optional components section at lines 90-95. Modify this if you need your image to contain additional items, for instance PowerShell or .NET Framework 4.

One further observation is that Macs don’t seem to be able to boot this version of Windows PE. I’m not sure whether this is a GOP display driver issue, or whether only true UEFI firmwares are required (Macs are EFI which is an earlier specification). To carry out an unattended Windows 8 install on a Mac via BootCamp you will need to build a Windows PE 3.0 ISO since Macs can’t PXE boot.

There’s some more info about UEFI booting on 32bit architectures here – apparently UEFI 2.3.1 compliance is a requirement. My VAIO’s Insyde H2O UEFI firmware certainly seems to ignore EFI loaders.

:: Build_WinPE.cmd
::
:: patters 2012
::
:: This script will build x86 and AMD64 Windows PE 4.0, automatically
:: collecting drivers from the relevant folders within the
:: unattended installation, building WIM and ISO images, and
:: will also upload the WIM images to the deployment server(s).
::
:: DO NOT cancel this script in progress as you can end up with
:: orphaned locks on files inside mounted WIM images which
:: usually require a reboot of the server to clear.
::

@echo off
setlocal ENABLEDELAYEDEXPANSION

::variables
     set SOURCE=\\WDSSERVER\unattended
     set PE_TEMP=C:\temp
     ::WinPE feature pack locale
     set PL=en-US
     ::commma separated list for WDS_SERVERS
     set WDS_SERVERS=WDSSERVER1,WDSSERVER2
::end variables

if "%PROCESSOR_ARCHITECTURE%"=="x86" set PRGFILES32=%PROGRAMFILES%
if "%PROCESSOR_ARCHITECTURE%"=="AMD64" set PRGFILES32=%PROGRAMFILES(X86)%

if not exist "%PRGFILES32%\Windows Kits\8.0\Assessment and Deployment Kit\Deployment Tools\*.*" (
     echo This script requires the Windows Assessment and Deployment Kit to be installed
     echo Download it from http://www.microsoft.com/en-us/download/details.aspx?id=30652
     echo.
     pause
     goto :eof
)
if "%1"=="relaunch" (
     call :BUILD_WINPE %2 %3 %4
     goto :eof
)
if "%1"=="unmount" (
     :: use this if you have a problem with the script and there are WIMs still mounted
     dism /Unmount-Wim /MountDir:"%PE_TEMP%\WinPE-x86\mount" /discard
     dism /Unmount-Wim /MountDir:"%PE_TEMP%\WinPE-AMD64\mount" /discard
     goto :eof
)
:prompt
cls
set /P SELECTION=Build WinPE for which CPU architecture (AMD64, x86, both)? [AMD64]: 
if "%SELECTION%"=="" set SELECTION=AMD64
if "%SELECTION%"=="amd64" set SELECTION=AMD64
if "%SELECTION%"=="X86" set SELECTION=x86
if "%SELECTION%"=="b" set SELECTION=both
if "%SELECTION%"=="BOTH" set SELECTION=both
if "%SELECTION%"=="AMD64" (
     start "Building Windows PE for AMD64 - NEVER CANCEL THIS SCRIPT IN PROGRESS" cmd /c "%0" relaunch AMD64
     goto :eof
)
if "%SELECTION%"=="x86" (
     start "Building Windows PE for x86 - NEVER CANCEL THIS SCRIPT IN PROGRESS" cmd /c "%0" relaunch x86
     goto :eof
)
if "%SELECTION%"=="both" (
     ::opening both instances of this script simultaneously seems to cause race conditions with dism.exe
     start /wait "Building Windows PE for x86 - NEVER CANCEL THIS SCRIPT IN PROGRESS" cmd /c "%0" relaunch x86 nopause
     start "Building Windows PE for AMD64 - NEVER CANCEL THIS SCRIPT IN PROGRESS" cmd /c "%0" relaunch AMD64
     goto :eof
)
goto :prompt

:BUILD_WINPE
set PE_ARCH=%1
set OSCDImgRoot=%PRGFILES32%\Windows Kits\8.0\Assessment and Deployment Kit\Deployment Tools\%PROCESSOR_ARCHITECTURE%\Oscdimg
set WinPERoot=%PRGFILES32%\Windows Kits\8.0\Assessment and Deployment Kit\Windows Preinstallation Environment
set DandIRoot=%PRGFILES32%\Windows Kits\8.0\Assessment and Deployment Kit\Deployment Tools
set DISMRoot=%PRGFILES32%\Windows Kits\8.0\Assessment and Deployment Kit\Deployment Tools\%PROCESSOR_ARCHITECTURE%\DISM
set PATH=%PATH%;%PRGFILES32%\Windows Kits\8.0\Assessment and Deployment Kit\Deployment Tools\%PROCESSOR_ARCHITECTURE%\Oscdimg
set PATH=%PATH%;%PRGFILES32%\Windows Kits\8.0\Assessment and Deployment Kit\Deployment Tools\%PROCESSOR_ARCHITECTURE%\BCDBoot
set PATH=%PATH%;%PRGFILES32%\Windows Kits\8.0\Assessment and Deployment Kit\Deployment Tools\%PROCESSOR_ARCHITECTURE%\DISM
set PATH=%PATH%;%PRGFILES32%\Windows Kits\8.0\Assessment and Deployment Kit\Windows Preinstallation Environment
echo on
rd /s /q %PE_TEMP%\WinPE-%PE_ARCH%
call copype.cmd %PE_ARCH% %PE_TEMP%\WinPE-%PE_ARCH%
::package path
set PP=%PRGFILES32%\Windows Kits\8.0\Assessment and Deployment Kit\Windows Preinstallation Environment\%PE_ARCH%\WinPE_OCs
::image path
set IP=%PE_TEMP%\WinPE-%PE_ARCH%\mount
echo on
dism /Mount-Wim /WimFile:"%PE_TEMP%\WinPE-%PE_ARCH%\media\sources\boot.wim" /Index:1 /MountDir:"%IP%"
dism /image:"%IP%" /Add-Package /PackagePath:"%PP%\WinPE-Scripting.cab"^
 /PackagePath:"%PP%\%PL%\WinPE-Scripting_%PL%.cab" /PackagePath:"%PP%\WinPE-WMI.cab"^
 /PackagePath:"%PP%\%PL%\WinPE-WMI_%PL%.cab" /PackagePath:"%PP%\WinPE-MDAC.cab"^
 /PackagePath:"%PP%\%PL%\WinPE-MDAC_%PL%.cab" /PackagePath:"%PP%\WinPE-HTA.cab"^
 /PackagePath:"%PP%\%PL%\WinPE-HTA_%PL%.cab" /PackagePath:"%PP%\WinPE-Dot3Svc.cab"^
 /PackagePath:"%PP%\%PL%\WinPE-Dot3Svc_%PL%.cab"
dism /image:"%IP%" /Add-Driver /driver:"%SOURCE%\drivers\WinPE-%PE_ARCH%\CURRENT" /Recurse
copy "%PRGFILES32%\Windows Kits\8.0\Assessment and Deployment Kit\Deployment Tools\%PE_ARCH%\BCDBoot\bootsect.exe" "%IP%\Windows"
copy /y "%SOURCE%\scripts\WinPE\*.*" "%IP%\Windows\System32"
copy "%SOURCE%\tools\WinPE-%PE_ARCH%\*.*" "%IP%\Windows\System32"
copy /y "%PRGFILES32%\Windows Kits\8.0\Assessment and Deployment Kit\Deployment Tools\%PE_ARCH%\DISM\imagex.exe" "%IP%\Windows\System32"
dism /Unmount-Wim /MountDir:"%IP%" /commit

::Mac OS BootCamp will look for autorun.inf in order to validate this disk as a Windows Installer CD
::adding this allows us to start unattended installs using WinPE
date /T > "%PE_TEMP%\WinPE-%PE_ARCH%\media\autorun.inf"

::bootable ISO includes both BIOS & EFI boot loaders
oscdimg -m -o -u2 -udfver102 -bootdata:2#p0,e,b"%PE_TEMP%\WinPE-%PE_ARCH%\fwfiles\etfsboot.com"#pEF,e,b"%PE_TEMP%\WinPE-%PE_ARCH%\fwfiles\efisys.bin" "%PE_TEMP%\WinPE-%PE_ARCH%\media" "%PE_TEMP%\WinPE-%PE_ARCH%\WinPE-40-%PE_ARCH%.iso"
@echo off

::rename the WIM file to avoid having multiple image files on the WDS server with the same filename
ren "%PE_TEMP%\WinPE-%PE_ARCH%\media\sources\boot.wim" boot_%PE_ARCH%.wim

if "%PE_ARCH%"=="x86" set WDS_ARCH=%PE_ARCH%
if "%PE_ARCH%"=="AMD64" set WDS_ARCH=X64
for %%i in (%WDS_SERVERS%) do (
     echo.
     echo Adding/updating boot image on WDS server: %%i
     :: try to add the image first, if that fails then replace existing
     wdsutil /Verbose /Progress /Add-Image /ImageFile:"%PE_TEMP%\WinPE-%PE_ARCH%\media\sources\boot-40-%PE_ARCH%.wim"^
      /Server:%%i /ImageType:Boot /Name:"Microsoft Windows PE 4.0 (%PE_ARCH%)" || wdsutil /Verbose /Progress /Replace-Image^
      /Image:"Microsoft Windows PE 4.0 (%PE_ARCH%)" /ImageType:Boot /Architecture:%WDS_ARCH% /ReplacementImage^
      /Name:"Microsoft Windows PE 4.0 (%PE_ARCH%)" /ImageFile:"%PE_TEMP%\WinPE-%PE_ARCH%\media\sources\boot-40-%PE_ARCH%.wim"^
      /Server:%%i
     echo.
)
::rename the WIM back again so bootable USB devices can be created
ren "%PE_TEMP%\WinPE-%PE_ARCH%\media\sources\boot-40-%PE_ARCH%.wim" boot.wim
echo *******************************************************************
echo WDS boot image(s) updated
echo.
echo A bootable ISO of this image has been created at:
echo   %PE_TEMP%\WinPE-%PE_ARCH%\WinPE-40-%PE_ARCH%.iso
echo.
echo To create a bootable USB key, use diskpart.exe to create a FAT32 partition
echo and mark it active, then copy the contents of this folder to its root:
echo   %PE_TEMP%\WinPE-%PE_ARCH%\media
echo.
echo FAT32 is required for EFI support.
echo.
if "%2"=="nopause" goto :eof
pause
goto :eof

Windows software deployment and update script

For many years I have used scripts of my own design to build workstations and to roll out software updates. At the time I created these I found that most of the tools which could accomplish these tasks were unwieldy. Group Policy software deployment in particular never really seemed fit for purpose since it extended login times so dramatically. My experience gained in a previous job spent packaging applications for deployment had taught me that all installed software populates consistent information in the Windows Registry, so in my current job I tended to audit this data directly via my scripts. This was saved into an SQL database from where it could be queried, or manipulated via a data source in Excel.

I’m working my notice period at the moment ready for a new job I’ll start in October, and so I’m going over the stuff I have created in the current job in order to prepare my handover documents. Mindful of the dependency my current employer has on these custom scripts I decided to get a quote for a Dell KACE solution, thinking that since it’s a Virtual Appliance, and since there are only 150 PCs here it shouldn’t be too expensive – after all it’s only really providing what my scripts already do (workstation builds, drivers, software deployment, and auditing). But here’s the thing – they wanted something like £13,000! (I can’t recall the precise figure). To put it in context this figure is around one third of the cost of replacing all the workstations with new ones, or say half the annual salary of an IT support technician – quite out of the question.

Unsurprisingly I have decided instead to simply tidy up my scripts to make them easier to use. Sure, you could accomplish these tasks with SCCM but that’s not free either. In an SME, why spend huge amounts of money on something that can be automated without much trouble using mechanisms that are built in. Heck, even the uninstall command line is stored in the registry for virtually all software – that’s how the Add/Remove Programs Control Panel works! And most software can be installed silently in the desired way provided you research the command line arguments to do so. It’s no accident that AppDeploy.com which was a great crowdsourced repository of this knowledge became KACE which was then acquired by Dell. It still exists, though the content doesn’t seem to be as well maintained as it was.

I have used a startup script written in VBScript to keep software up to date on workstations. A startup script runs as the SYSTEM account so permissions are not an issue. Since I also maintain an unattended installation I already have a package folder with all the scripts to install each package. All I needed to code was a way to audit the Registry for each package and add some logic around that. Up until now, I had tended to write sections of the script specifically tailored for each package, and from there it’s not much of a stretch to apply packages to a workstation based on its OS version, or Active Directory OU or group membership. For the script I have published below, I have recreated this logic as a single function which can be invoked with a one line entry for each package (see the highlighted part) – everything else is taken care of. I hope it helps someone to save £13,000 :)

 

Sample script output

Running software package check for Adobe Flash Player...
  Registry data found at branch "Adobe Flash Player ActiveX"
  Comparing detected version 11.3.300.271 against desired version 11.4.402.265
  Removing old version 11.3.300.271
    Killing iexplore.exe
    Override detected, running "u:\packages\flash\uninstall_flash_player.exe -uninstall"
    u:\packages\flash\uninstall_flash_player.exe -uninstall
  Installing Adobe Flash Player 11.4.402.265

Running software package check for Paint.NET...
  Registry data found at branch "{529125EF-E3AC-4B74-97E6-F688A7C0F1C0}"
  Comparing detected version 3.60.0 against desired version 3.60.0
  Paint.NET is already installed and up to date.

Running software package check for Adobe Reader...
  Registry data found at branch "{AC76BA86-7AD7-1033-7B44-AA0000000001}"
  Comparing detected version 10.0.0 against desired version 10.1.4
  Removing old version 10.0.0
    Using UninstallString from the Registry, plus "/qb-!"
    MsiExec.exe /I{AC76BA86-7AD7-1033-7B44-AA0000000001} /qb-!
  Installing Adobe Reader 10.1.4

Running software package check for Photo Gallery...
  Registry data found at branch "{60A1253C-2D51-4166-95C2-52E9CF4F8D64}"
  Comparing detected version 16.4.3503.0728 against desired version 16.4.3503.0728
  Photo Gallery is already installed and up to date.

Running software package check for Mendeley Desktop...
  Installing Mendeley Desktop 1.6
 

The script

'startup.vbs
'patters 2006-2012

Option Explicit
Dim objNetwork, objShell, objReg, strKey, colProcess, objProcess, arrSubKeys 
Dim strFileServer
Const HKEY_CURRENT_USER = &H80000001
Const HKEY_LOCAL_MACHINE = &H80000002

'set up objects
Set objNetwork = CreateObject("WScript.Network")
Set objShell = CreateObject("WScript.Shell")
Set objReg = GetObject("winmgmts:{impersonationLevel=impersonate}!\\.\root\default:StdRegProv")

strFileServer = "YOURSERVERHERE"
MapNetworkDrive "U:","unattended"

Package "flash.cmd", "Adobe Flash Player", "11.4.402.265", "u:\packages\flash\uninstall_flash_player.exe -uninstall", False, True, "iexplore.exe"    
Package "paintnet.cmd", "Paint.NET", "3.60.0", "/qb-!", False, False, "" 
Package "adobe.cmd", "Adobe Reader", "10.1.4","/qb-!",False, False, array("outlook.exe","iexplore")
Package "photogal.cmd", "Photo Gallery", "16.4.3503.0728", "/qb-!", False, False, "iexplore.exe"
Package "mendeley.cmd", "Mendeley Desktop", "1.6", "/S", True, False, "winword.exe"

objNetwork.RemoveNetworkDrive "U:", True, True
WScript.Echo VbCrLf & "Finished software checks"


Function Package(strPackageName, strTargetDisplayName, strTargetVersion, strExtraUninstParams, boolExtraUninstQuotes, boolUninstForceOverride, ProcessToKill)

  '=============================================================================

  'To understand this function you need to know that installed software packages
  'will populate keys below these branches of the Registry:
  '  HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall
  '  HKLM\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall
  '    (the latter for 32bit software on 64bit Windows)
  'This is the data that is mined when you look at Add/Remove Programs
  'in the Control Panel 

  'strPackageName is the package script on your package server (e.g. flash.cmd)

  'strTargetDisplayName can be a full or partial match of the Registry key
  'DisplayName (matches from the left)
  '  "Java(TM)" would match "Java(TM) 6 Update 5" and all other versions

  'strTargetVersion is the full version number from DisplayVersion in the Registry
  'Each decimal point of precision will be compared in turn.

  'If the Registry key DisplayVersion is not used by a package, the same number
  'of digits is parsed from the right hand side of the DisplayName string

  'strExtraUninstParams is used when you want to override the command line
  'specified by QuietUninstallString in the Registry, or for when that value is
  'missing for example, sometimes InnoSetup packages will specify the switch
  '/SILENT in QuietUninstallString, but you may need to override by appending
  '/VERYSILENT to the command line in UninstallString
  'If neither QuietUninstallString and UninstallString are present, the script
  'will use strExtraUninstParams as the full uninstall command line
  
  'Some packages define UninstallString as a long filename but forget to
  'surround it with quotes. You can correct this by setting
  'boolExtraUninstQuotes = True
  '   Package "mendeley.cmd", "Mendeley Desktop", "1.6", "/S", True, False, "winword.exe"

  'In some cases you may want to ignore the value of both QuietUninstallString
  'and UninstallString and override the command completely. To do this, set
  'boolUninstForceOverride to True
  '   Package "flash.cmd", "Adobe Flash Player", "11.4.402.265", "u:\packages\flash\uninstall_flash_player.exe -uninstall", False, True, "iexplore.exe"

  'Finally, ProcessToKill is a string or array containing the name(s) of any
  'running process(es) you need to kill, if plugins are being installed for Word
  'or Internet Explorer for instance.

  '=============================================================================

  Dim arrBranches, strBranch, boolRemoval, strActualDisplayName, strActualVersion
  Dim strQuietUninstall, strUninstall
  WScript.Echo VbCrLf & "Running software package check for " & strTargetDisplayName & "..."
  'we need to iterate through both the 32 and 64bit uninstall branches of the Registry
  arrBranches = Array("SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\", "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\")
  For Each strBranch In arrBranches
    'firstly, remove old version of package if it's present
    objReg.EnumKey HKEY_LOCAL_MACHINE, strBranch, arrSubKeys
    If IsArray(arrSubkeys) Then
      For Each strKey in arrSubkeys
        objReg.GetStringValue HKEY_LOCAL_MACHINE, strBranch & strKey, "DisplayName", strActualDisplayName
        If Left(strActualDisplayName, Len(strTargetDisplayName)) = strTargetDisplayName Then
          'we've found the target software package
          WScript.Echo "  Registry data found at branch """ & strKey & """"
          'is there a version string (not all software will have one)?
          objReg.GetStringValue HKEY_LOCAL_MACHINE, strBranch & strKey, "DisplayVersion", strActualVersion
          If Not IsNull(strActualVersion) Then
          Else
            'if there's no version string we'll try to grab the same number of chars from the right hand side of the DisplayName string  
            strActualVersion = Right(strActualDisplayName, Len(strTargetVersion))
          End If
          If (IsUpgradeNeeded (strActualVersion,strTargetVersion)) = True Then
            strQuietUninstall = ""
            WScript.Echo "  Removing old version " & strActualVersion
            KillProcess ProcessToKill
            'check the package's registry settings
            objReg.GetStringValue HKEY_LOCAL_MACHINE, strBranch & strKey, "UninstallString", strUninstall
            objReg.GetStringValue HKEY_LOCAL_MACHINE, strBranch & strKey, "QuietUninstallString", strQuietUninstall
            If Not strExtraUninstParams = "" Then
              'Extra parameters were sent to the function
              If boolUninstForceOverride = True Then
                'Entire uninstall command line was forced so use strExtraUninstParams, regardless of what's in the Registry
                WScript.Echo "    Override detected, running """ & strExtraUninstParams & """"
                WScript.Echo "    " & strExtraUninstParams
                WinExec strExtraUninstParams
              ElseIf Not IsNull(strUninstall) Then
                'use the basic UninstallString plus the additional parameters
                If boolExtraUninstQuotes = True Then
                  strUninstall = """" & strUninstall & """"
                End If
                strUninstall = strUninstall & " " & strExtraUninstParams
                WScript.Echo "    Using UninstallString from the Registry, plus """ & strExtraUninstParams & """"
                WScript.Echo "    " & strUninstall
                WinExec strUninstall
              Else
                'no UninstallString was found in the Registry, so assume that strExtraUninstParams is the full removal command line
                WScript.Echo "    No UninstallString found, running """ & strExtraUninstParams & """"
                WScript.Echo "    " & strExtraUninstParams
                WinExec strExtraUninstParams
              End If
            Else
              'No extra parameters were sent to the function
              'if there's already a value for QuietUninstallString then use that command line
              If Not IsNull(strQuietUninstall) Then
                WScript.Echo "    Using QuietUninstallString directly from the Registry"
                WScript.Echo "    " & strQuietUninstall
                WinExec strQuietUninstall
              ElseIf Not IsNull(strUninstall) Then
                'no QuietUninstallString was found, fall back to UninstallString
                If boolExtraUninstQuotes = True Then
                  strUninstall = """" & strUninstall & """"
                End If
                WScript.Echo "    Using UninstallString directly from the Registry"
                WScript.Echo "    " & strUninstall
                WinExec strUninstall
              Else
                WScript.Echo "    ERROR - this package doesn't seem to have any UninstallString defined - you'll need to send one to the Package function (see script source for details)"
                Exit Function
              End If
            End If
          Else
            'IsUpgradeNeeded (strActualVersion,strTargetVersion) is False
            'package was detected, but version is >= than the one specified
            WScript.Echo "  " & strTargetDisplayName & " is already installed and up to date."
            Exit Function
          End If
        End If
      Next
    End If
  Next
  'install package
  WScript.Echo "  Installing " & strTargetDisplayName & " " & strTargetVersion
  KillProcess ProcessToKill
  WinExec "U:\packages\" & strPackageName
End Function


Function IsUpgradeNeeded(strVerActual,strVerDesired)
  Dim arrActualVersion, arrDesiredVersion, i
  'Break software version down on decimal points
  arrActualVersion = split(strVerActual,".")
  arrDesiredVersion = split(strVerDesired,".")
  WScript.Echo "  Comparing detected version " & strVerActual & " against desired version " & strVerDesired
  'iterate, comparing each sub-version number starting from left
  For i = 0 To UBound(arrActualVersion)
    'WScript.Echo "  comparing digit... is " & arrActualVersion(i) & " less than " & arrDesiredVersion(i) 
    If arrActualVersion(i) < arrDesiredVersion(i) Then
      'installed version is out of date
      IsUpgradeNeeded = True
      Exit Function
    ElseIf arrActualVersion(i) > arrDesiredVersion(i) Then
      'installed version is newer
      IsUpgradeNeeded = False
      Exit Function     
    End If
  Next
  'thus far the version numbers are the same, but there may be additional
  'decimal points of precision in the desired version
  '  e.g. Adobe Reader 10.1.4 is newer than 10.1
  If UBound(arrDesiredVersion) > UBound(arrActualVersion) Then
    IsUpgradeNeeded = True
  Else
    IsUpgradeNeeded = False
  End If
End Function


Function MapNetworkDrive(strDriveLetter, strSharePath)
  On Error Resume Next
  'if the share name is not a UNC path, assume it's on the normal fileserver
  If Not Left(strSharePath,2) = "\\" Then
    strSharePath = "\\" & strFileServer & "\" & strSharePath
  End If
  If objFSO.DriveExists(strDriveLetter) Then
    objNetwork.RemoveNetworkDrive strDriveLetter, True, True
  End If
  objNetwork.MapNetworkDrive strDriveLetter, strSharePath
  If Err.Number <> 0 Then
    WScript.Echo "Error - " & Err.Description
    Err.Clear
  End If
  On Error Goto 0
End Function


Function WinExec(strExec)
  Dim objExec, eTime
  WinExec = True
  Set objExec = objShell.Exec(strExec)
  eTime = DateAdd("s", 120, Now)
  Do While objExec.Status = 0
    WScript.Sleep 1000
  Loop
End Function


Function KillProcess(Process)
  Dim strProcessElement
  If IsArray(Process) Then
    For Each strProcessElement in Process
      KillIndividualProcess(strProcessElement)
    Next
  ElseIf Not Process = "" Then
    KillIndividualProcess(Process)
  End If
End Function


Function KillIndividualProcess(strProcess)
  Dim colProcess, objProcess
  Set colProcess = objWMI.ExecQuery("Select * from Win32_Process")
  For Each objProcess in colProcess
    If LCase(objProcess.Name) = LCase(strProcess) Then
      WScript.Echo "    Killing " & strProcess
      'occasionally one parent process may kill all children leading to an object error
      'so disable error handling temporarily
      On Error Resume Next
      objProcess.Terminate()
      On Error Goto 0
    End If
  Next
End Function

Deploying Windows Photo Gallery 2012

Windows-Photo-Gallery

Though it seems primarily pitched at home users, Microsoft’s Windows Photo Gallery is a useful image management tool even in a professional environment. It’s distributed as part of a suite of software known collectively as Windows Essentials 2012. I don’t understand why these tools aren’t included in Windows itself, but since they were until recently part of the Live family I’m presuming that they were designed to encourage the use of Microsoft’s online services. The apparent home user bias to the setup (a single installer for the whole suite, which downloads on demand, which asks for a Live sign-in, and which alters homepage and search provider) consequently makes Photo Gallery quite difficult to deploy and automate.

Firstly the proper offline installer package is tucked away here on Microsoft’s website.

The next issue is that the silent install switches don’t seem to be officially documented by Microsoft. I was able to piece together the working command line using a TechNet forum post, this blog post about deploying the 2011 version, and some stuff on the MSFN forum.

What held me up for a while is that you can no longer target only the Photo Gallery app – MovieMaker and Photo Gallery are bundled together with 2012. So I arrived at this one-liner which I invoke from a more complex workstation startup script, if it’s needed:

start /wait WLSetup-all.exe /q /r:n /NOToolbarCEIP /NOhomepage /nolaunch /nosearch /AppSelect:MovieMaker /log:%TEMP%\WLEsetup.log

The HKCU registry customizations are pretty much the same as for the 2011 version, so to suppress the EULA and Microsoft account sign-in prompt, and to prevent nags about file type associations you will need to set the following in your login script (this is an extract from my VBScript one, but it’s pretty human-readable):

...
'default preferences for Microsoft Photo Library (agree EULA, don't steal filetype associations, no Windows Live sign-in)
objReg.CreateKey HKEY_CURRENT_USER,"Software\Microsoft\Windows Live"
objReg.CreateKey HKEY_CURRENT_USER,"Software\Microsoft\Windows Live\Common"
objReg.SetStringValue HKEY_CURRENT_USER,"Software\Microsoft\Windows Live\Common","TOUVersion","16.0.0.0"
objReg.CreateKey HKEY_CURRENT_USER,"Software\Microsoft\Windows Live\Photo Gallery"
objReg.SetDWORDValue HKEY_CURRENT_USER,"Software\Microsoft\Windows Live\Photo Gallery","SignInRemindersLeft","0"
objReg.CreateKey HKEY_CURRENT_USER,"Software\Microsoft\Windows Live\Photo Gallery\Library"
arrStringValues = Array(".WDP",".BMP",".JFIF",".JPEG",".JPE",".JPG",".PNG",".TIF",".DIB",".TIFF",".ICO")
objReg.SetMultiStringValue HKEY_CURRENT_USER,"Software\Microsoft\Windows Live\Photo Gallery\Library", "DontShowAssociationsDialogExtensions", arrStringValues
...

To install on a Windows 8 workstation you’ll need the .Net Framework 3.5 “feature” to be installed, which isn’t there by default (Control Panel > Programs > Uninstall a program > Turn Windows Features on or off). This is problematic if you’re using WSUS – the attempt to download the update will fail with error 0x800f0906. Microsoft have an MDSN article about this, but the prescribed fix of using DISM to fetch the feature from the install media didn’t work for me on Windows 8 Enterprise. I had to remove my PC from an OU which inherits WSUS settings, run gpupdate /force then try again, this time successfully.

In my organization, the requirement for Photo Gallery is for users to interact with a centralized image library. This is stored on a Window 2008 R2 server, and I discovered that I could not add this folder to the Pictures library unless it was indexed on the server side (well, without enabling offline folders – which I don’t want). The relevant information on this topic can be found in this Technet post. In summary, you need to enable the Windows Search Service on the file server, which is a “Role Service” under the File Services role in Server Manager.

The missing piece of the puzzle so far is how to programmatically add this image repository location to each user’s Pictures library. I found a page about this, though the tools did not seem to actually work. Admittedly it’s a few years old, so maybe there are some more official tools now. More research to follow…