newspaint

Documenting Problems That Were Difficult To Find The Answer To

Using HTML::Mason With CGI Provider

So you want to use HTML::Mason (version 1) but your web provider gives you cPanel-like access to CGI scripting only?

Download HTML::Mason from CPAN and extract the contents from the /lib directory into your account, say, into a directory called /lib/perl/mason.

Then create a file, /public_html/cgi-bin/mason_handler.cgi, which contains:

#!/usr/bin/perl

use lib $ENV{"DOCUMENT_ROOT"} . "/../lib/perl/mason";
use HTML::Mason::CGIHandler;

my $h = HTML::Mason::CGIHandler->new(
  data_dir => '/tmp/mason_data',
  allow_globals => [qw(%session $u)],
);

$h->handle_request;

Now you want to configure your Apache to use this handler for Perl Mason webpages in the /public_html/mason directory (Apache v2.2):

<Directory /public_html/mason>
  <FilesMatch "\.html$">
    Action html-mason /cgi-bin/mason_handler.cgi
    SetHandler html-mason

    # for Apache 2.2
    Order allow,deny
    Allow from all

    # for Apache 2.4 (see https://httpd.apache.org/docs/2.4/upgrading.html)
    #Require all granted
  </FilesMatch>

  <FilesMatch "^(autohandler|dhandler)$">
    Action html-mason /cgi-bin/mason_handler.cgi
    SetHandler html-mason

    # for Apache 2.2
    Order allow,deny
    Allow from all

    # for Apache 2.4 (see https://httpd.apache.org/docs/2.4/upgrading.html)
    #Require all granted
  </FilesMatch>
</Directory>

Some CGI website providers require additional Perl modules for HTML::Mason to work, these can all be downloaded and extracted from CPAN:

  • Exception/Class.pm
  • Devel/StackTrace.pm
  • Class/Container.pm
  • Class/Data/Inheritable.pm
  • Params/Validate.pm *
  • Params/ValidatePP.pm *

(the files marked with a * are those that can be downloaded from CPAN and use the command perl Makefile –pm to force native perl code generation).

Using PowerShell 2.0 With Selenium to Automate Internet Explorer, Firefox, and Chrome

PowerShell 2.0 on Windows XP/7 uses .Net 3.5 so the first thing to do is download the Selenium WebDriver.dll file from Selenium’s download page and extract the net35/ directory.

Internet Explorer

Next you want to obtain the Internet Explorer driver from this site. I recommend version 2.41 because “as of 15 April 2014, IE 6 is no longer supported”. This must reside in your current PATH so in your script you may want to modify your PATH to ensure the executable (IEDriverServer.exe) can be found there. If you’re wondering whether to get the 32-bit or the 64-bit version, start with the 32-bit even if you’ve got a 64-bit Windows.

At this point you’ll want to quickly instantiate Internet Explorer and navigate somewhere. Great. Let’s do it.

# Load the Selenium .Net library
Add-Type -Path "N:\selenium\WebDriver.dll" # or wherever your WebDriver.dll is

# Set the PATH to ensure IEDriverServer.exe can found
$env:PATH += ";N:\selenium"

# Instantiate Internet Explorer
$ie_object = New-Object "OpenQA.Selenium.IE.InternetExplorerDriver"

This outputs:

New-Object : Exception calling ".ctor" with "0" argument(s): "Request for the permission of type 'System.Net.SocketPermission, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' failed."
At line:1 char:17
+ $ie = New-Object <<<<  "OpenQA.Selenium.IE.InternetExplorerDriver"
    + CategoryInfo          : InvalidOperation: (:) [New-Object], MethodInvocationException
    + FullyQualifiedErrorId : ConstructorInvokedThrowException,Microsoft.PowerShell.Commands.NewObjectCommand

Wait, what’s this? I don’t know. I just don’t know. It will happen if the DLL is on a network drive and not marked as “trusted” (whatever that means). So copy the DLL onto a local hard drive and try again.

# Load the Selenium .Net library
Add-Type -Path "C:\selenium\WebDriver.dll" # put your DLL on a local hard drive!

# Set the PATH to ensure IEDriverServer.exe can found
$env:PATH += ";N:\selenium"

# Instantiate Internet Explorer
$ie_object = New-Object "OpenQA.Selenium.IE.InternetExplorerDriver"

Great! Now we have an Internet Explorer window appear. We can navigate to a new URL:

$ie_object.Navigate().GoToURL( "http://www.bbc.co.uk/languages" )

This worked! The call won’t return until the page download is complete.

Next let’s click on a link from the link text:

$link = $ie_object.FindElementByLinkText( "Spanish" )
$link.Click()

# display current URL
$ie_object.Url

FireFox

Let’s try it with FireFox now. We require the GeckoDriver from the Selenium downloads page. Note that there is no GeckoDriver support for Windows XP at all.

# Set the PATH to ensure geckodriver.exe can found
$env:PATH += ";N:\selenium"

$ff_object = New-Object "OpenQA.Selenium.Firefox.FirefoxDriver"

Chrome

Finally let’s try with Google Chrome. We require the ChromeDriver from the Selenium downloads page.

# Set the PATH to ensure chromedriver.exe can found
$env:PATH += ";N:\selenium"

$chrome_object = New-Object "OpenQA.Selenium.Chrome.ChromeDriver"

How to Redirect Stderr From Executable in PowerShell 2.0 Without Line Breaks at Console Width

So you think PowerShell is flexible and useful and you go to redirect output from an external command to a file. You’ll parse this file later. But when you do you are horrified to find line breaks inserted in the middle of lines in the file… at the width of the console in which your PowerShell session was running.

The problem is that PowerShell is so smart that it unhelpfully inserts line breaks in all output at the current console width.

There are a few suggested answers to this including adding a -Width parameter to Out-File, but this doesn’t solve the issue for stderr redirects.

The solution was found in this post. Simply put, run your external command through cmd.exe and redirect from there!

To ensure the redirect operator is passed to cmd.exe escape the angle bracket with a backtick.

cmd.exe /C wget.exe -O - http://www.google.com/ 2`>stderr.log

Using wget to Automate Logging Into Websites

The open-source wget tool is useful for automating website access/scraping. In particular because it can store/retrieve cookies from a file.

# create a name for the cookie jar/file
COOKIE_JAR=/tmp/cookies.$$.txt

# save cookies from homepage access
wget --spider --save-cookies $COOKIE_JAR --keep-session-cookies http://www.smrt.com.sg/

# now submit request using saved cookies
wget -O - \
  --load-cookies $COOKIE_JAR \
  --save-cookies $COOKIE_JAR \
  --keep-session-cookies \
  --header "Referer: http://journey.smrt.com.sg/" \
  --post-data='startlat=1.357348601&startlng=103.9884093&endlat=1.276243657&endlng=103.8545958&routeopt=fastest&start_type=mrt&end_type=mrt&mode=TRANSIT&use_lrt=yes' \
  https://connect.smrt.wwprojects.com/smrt/api/journey/

Note that –spider performs a HEAD request and does not download the response. Options useful for debugging and seeing what is sent/received are -d and -S. For cookies the –keep-session-cookies option is essential to save session cookies (with no expiry time set) to the cookie file.

Serialising Arrays and Hashes in PowerShell 2.0

So you want to Invoke-Command a scriptblock on another Windows computer but are struggling to communicate results back to the caller because of a lack of serialisation routines in PowerShell 2.0? Yes, PowerShell 3.0 did introduce the ConvertFrom-Json and ConvertTo-Json cmdlets. But if you’re stuck on PowerShell 2.0 then you need another way to send hashes and lists.

Non-Recursive

Why not convert your hash-and-array data structure into a string – one that can be parsed by Invoke-Expression? This is a function that will do exactly that – and it is non-recursive for reasons that will be explained further down (and there’s a simpler recursive function provided later, too):

Function Serialise-Object {
    Param( $Root )

    Function AddAfter-ListNode {
        Param( $LinkedList, $AfterNode, $NewNode )
        if ( $AfterNode -eq $null ) {
            $LinkedList.AddLast( $NewNode )
        } else {
            $LinkedList.AddAfter( $AfterNode, $NewNode )
        }
    }

    Function Escape-SingleQuoted {
        Param( $Source )
        $Source -replace "'", "''"
    }

    # create lists
    $TodoStack = New-Object "System.Collections.Generic.Stack[Object]"
    $StringsList = New-Object "System.Collections.Generic.LinkedList[String]"

    # set up first element
    $TodoStack.Push( @( $Root, $StringsList.Last ) )

    while ( $true ) {
        try {
            $NextTodo = $TodoStack.Pop()
        } catch {
            break
        }

        ( $Item, $Node ) = @( $NextTodo )
        if ( $Item -eq $null ) {
            $NewStringNode = New-Object "System.Collections.Generic.LinkedListNode[String]" "`$null"
            AddAfter-ListNode $StringsList $Node $NewStringNode
        } elseif ( $Item.getType().FullName -eq "System.Collections.Hashtable" ) {
            $NewStringNode = New-Object "System.Collections.Generic.LinkedListNode[String]" "@{"
            AddAfter-ListNode $StringsList $Node $NewStringNode
            $LastStringNode = $NewStringNode

            $First = $true
            $Item.Keys |ForEach-Object {
                $keyname = ""
                if ( $First ) {
                    $First = $false
                } else {
                    $keyname += ";"
                }
                $keyname += $( "'" + (Escape-SingleQuoted $_) + "'=" )

                $NewStringNode = New-Object "System.Collections.Generic.LinkedListNode[String]" $keyname
                AddAfter-ListNode $StringsList $LastStringNode $NewStringNode
                $LastStringNode = $NewStringNode

                $TodoStack.Push( @( $Item[$_], $LastStringNode ) )
            }

            $NewStringNode = New-Object "System.Collections.Generic.LinkedListNode[String]" "}"
            AddAfter-ListNode $StringsList $LastStringNode $NewStringNode
        } elseif ( $Item.getType().FullName -eq "System.Object[]" ) {
            $NewStringNode = New-Object "System.Collections.Generic.LinkedListNode[String]" "@("
            AddAfter-ListNode $StringsList $Node $NewStringNode
            $LastStringNode = $NewStringNode

            $First = $true
            $Item |ForEach-Object {
                if ( $First ) {
                    $First = $false
                } else {
                    $NewStringNode = New-Object "System.Collections.Generic.LinkedListNode[String]" ","
                    AddAfter-ListNode $StringsList $LastStringNode $NewStringNode
                    $LastStringNode = $NewStringNode
                }

                $TodoStack.Push( @( $_, $LastStringNode ) )
            }

            $NewStringNode = New-Object "System.Collections.Generic.LinkedListNode[String]" ")"
            AddAfter-ListNode $StringsList $LastStringNode $NewStringNode
        } else {
            if ( $Item.GetType().FullName -eq "System.String" ) {
                $NewStringNode = New-Object "System.Collections.Generic.LinkedListNode[String]" $( "'" + (Escape-SingleQuoted $Item) + "'" )
                AddAfter-ListNode $StringsList $Node $NewStringNode
            } else {
                $NewStringNode = New-Object "System.Collections.Generic.LinkedListNode[String]" $( "[" + $Item.GetType().FullName + "]'" + (Escape-SingleQuoted $Item.ToString()) + "'" )
                AddAfter-ListNode $StringsList $Node $NewStringNode
            }
        }
    }

    @(
        $StringsList.GetEnumerator() |ForEach-Object { $_ }
    ) -join ""
}

Some examples of output:

> Serialise-Object $null
$null

> Serialise-Object 14.25
[System.Double]'14.25'

> Serialise-Object "Four o'clock"
'Four o''clock'

> Serialise-Object @( "First", "Second", @( "Inner1", "Inner2" ) )
@('First','Second',@('Inner1','Inner2'))

> Serialise-Object @{ "ArrayA" = @( 1, 2.5 ); "ArrayB" = @( 'e', 'f', 'g' ) }
@{'ArrayA'=@([System.Int32]'1',[System.Double]'2.5');'ArrayB'=@('e','f','g')}

> Serialise-Object @{ "OuterH" = @{ "InnerH" = @{ "key1" = [long]0xff } } }
@{'OuterH'=@{'InnerH'=@{'key1'=[System.Int64]'255'}}}

The resulting string can be fed directly into Invoke-Expression and the result is going to be very similar if not identical to the serialised object.

So, how does it work? It iterates over the object it is given. If it is a simple scalar type ($null, string, or non-list/hash) then it is converted to a string, prepended with its type if not a string, and output. If the object is an array or hash then each element or element-pair is iterated through and that object is recursively processed.

This function would have looked a lot simpler as a recursive function. So why was it implemented using lists and stacks instead of recursion? Because I wanted to send this function as a string through an Invoke-Command cmdlet and have it rebuilt as a scriptblock on the remote side; but one problem – how does one call an anonymous scriptblock recursively? Perhaps there’s a way but I don’t know how.

For example:

$remote_scriptblock = {
    Param( [String]$FnSerialiseStr )

    $FnSerialise = [scriptblock]::create( $FnSerialiseStr )

    $Start = Get-Date
    Start-Sleep -Milliseconds 1500

    & $FnSerialise @{ "time"=((Get-Date) - $Start).TotalSeconds }
}

Invoke-Command $remote_scriptblock -ArgumentList @(${function:Serialise-Object})

This outputs:

@{'time'=[System.Double]'1.5'}

Pretty neat, huh? You can send this function to the other side and run it!

Recursive

Ah.. but what if you want to send several named functions to the other side?

$remote_scriptblock = {
    Param( [String]$PreBlockStr )

    $PreBlock = [scriptblock]::create( $PreBlockStr )
    & $PreBlock

    $Start = Get-Date
    Start-Sleep -Milliseconds 2500

    Serialise-Object @{ "time"=((Get-Date) - $Start).TotalSeconds }
}
Invoke-Command $remote_scriptblock -ArgumentList @("Function Serialise-Object { ${function:Serialise-Object} }")

This outputs:

@{'time'=[System.Double]'2.5'}

Well that solves the problem of a recursive function trying to call itself.

Let’s rewrite the serialisation function as the simpler recursive form:

Function Serialise-Object {
    Param( $Root )

    Function Escape-SingleQuoted {
        Param( $Source )
        $Source -replace "'", "''"
    }

    if ( $Root -eq $null ) {
        "`$null"
    } elseif ( $Root.getType().FullName -eq "System.Collections.Hashtable" ) {
        $out = "@{"

        $First = $true
        $Root.Keys |ForEach-Object {
            if ( $First ) {
                $First = $false
            } else {
                $out += ";"
            }

            $out += $( "'" + (Escape-SingleQuoted $_) + "'=" )
            $out += Serialise-Object $Root[$_]
        }
        $out + "}"
    } elseif ( $Root.getType().FullName -eq "System.Object[]" ) {
        $out = "@("

        $First = $true
        $Root |ForEach-Object {
            if ( $First ) {
                $First = $false
            } else {
                $out += ","
            }

            $out += Serialise-Object $_
        }
        $out + ")"
    } else {
        if ( $Root.GetType().FullName -eq "System.String" ) {
            $( "'" + (Escape-SingleQuoted $Root) + "'" )
        } else {
            $( "[" + $Root.GetType().FullName + "]'" + (Escape-SingleQuoted $Root.ToString()) + "'" )
        }
    }
}

With this simpler code we can use it remotely as follows:

$remote_scriptblock = {
    Param( [String]$PreBlockStr )

    $PreBlock = [scriptblock]::create( $PreBlockStr )
    & $PreBlock

    $Start = Get-Date
    Start-Sleep -Milliseconds 3500

    Serialise-Object @{ "time"=((Get-Date) - $Start).TotalSeconds }
}
Invoke-Command $remote_scriptblock -ArgumentList @("Function Serialise-Object { ${function:Serialise-Object} }")

This outputs:

@{'time'=[System.Double]'3.5'}

… using simpler code.

Allowing Powershell Scripts to Run on Windows

So you want to run a PowerShell script on your Windows system but get the following message:

File myscript.ps1 cannot be loaded because the execution of scripts is disabled on this system. Please
see "get-help about_signing" for more details.
At line:1 char:23
+ .\myscript.ps1 <<<<
    + CategoryInfo          : NotSpecified: (:) [], PSSecurityException
    + FullyQualifiedErrorId : RuntimeException

A fix is to change the execution policy. To see the current state of execution policies on your system:

PS C:\> Get-ExecutionPolicy -List |Format-Table -AutoSize

        Scope ExecutionPolicy
        ----- ---------------
MachinePolicy       Undefined
   UserPolicy       Undefined
      Process       Undefined
  CurrentUser       Undefined
 LocalMachine       Undefined

If you do not have administrator rights on your system the easiest way to allow scripts to be executed is to allow the current user to run scripts.

PS C:\> Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned

Execution Policy Change
The execution policy helps protect you from scripts that you do not trust. Changing the execution policy might expose
you to the security risks described in the about_Execution_Policies help topic. Do you want to change the execution
policy?
[Y] Yes  [N] No  [S] Suspend  [?] Help (default is "Y"): y

Note that “RemoteSigned” means that all scripts and configuration files downloaded from the Internet must be signed by a trusted publisher, but locally authored scripts can be run without a signature.

Using Perl to Make Signed Requests to Public Transport Victoria Timetable API

So you want to create an application to access the Public Transport Victoria (PTV) Timetable API.

You’ve followed the instructions and e-mailed a request for a developer ID and an access key; and you’ve received both in an e-mail that contained text similar to the following:

Thank you for your interest in the PTV Timetable API.

Your email address has now been registered and your user Id and API key are
below.

User Id: 2912345

API Key: 4cc12345-ff11-2222-a00a-dd1297cd04aa

Now you want to create a signed request using Perl to access the API.

The following function will take a URL and return a signed URL that can be used to access that URL:

#!/usr/bin/perl -w

use Digest::HMAC;
use Digest::SHA;

use strict;

my $devid = "2912345";
my $apikey = "4cc12345-ff11-2222-a00a-dd1297cd04aa";

sub sign_url {
  my ( $url ) = @_;

  # add ?devid=$devid or &devid=$devid to URL
  $url .= ( index($url, "?") < $[ ) ? "?" : "&";
  $url .= "devid=$devid";

  # strip out base URL from part used in signing
  my $request = $url;
  $request =~ s{^(([a-z]+)://+)?[^/]+}{};

  # calculate signature using API key and URL without base
  #   e.g. sign over a string like "/v3/routes?devid=2912345"
  my $signature = Digest::HMAC::hmac_hex(
      $request,
      $apikey,
      \&Digest::SHA::sha1
  );

  $url .= "&signature=" . $signature;
  return $url;
}

print sign_url( "http://timetableapi.ptv.vic.gov.au/v3/routes" );

This should output the following:

$ perl -w signtest.pl
http://timetableapi.ptv.vic.gov.au/v3/routes?devid=2912345&signature=b7ee928f05499a0016746daef5013dba35224d8e

The example is using an invalid signature and devid, so this example URL will not actually return a page; you will have to provide the access key and devid you received in your e-mail.

Note: it is absolutely essential that you do not change the capitalisation of the access key in this script. While it looks like a hex string it is actually treated as case-sensitive text.

Bash Script to Run Apt-Get Upgrades on All LXC Running Containers

If you have several LXC containers on your system that you want to upgrade in a loop you could use a sensible infrastructure management platform like Ansible.

Here, however, is a simple BASH script for logging into each running LXC host one at a time and letting you answer yes or no to the apt-get upgrade taking place:

#!/bin/bash

# declare an array variable
declare -a LINES

# store each line about running LXC containers into array
LINENUM=0
while IFS= read -r line; do
  echo "  processing LINENUM=$LINENUM, $line"
  LINES[$LINENUM]="$line"
  LINENUM=$((LINENUM + 1))
done <<< "$(sudo lxc-ls -f)"

# iterate through each line in the array
INDEX=0
while [[ $INDEX -lt $LINENUM ]]; do
  # squash up the spaces and delete commas from line
  CLEANLINE=`echo "${LINES[$INDEX]}" |tr -s ' ' |tr -d ,`

  # extract the hostname and ipaddress fields
  HNAME=`echo "$CLEANLINE" |cut '-d ' -f1`
  IPADDR=`echo "$CLEANLINE" |cut '-d ' -f5`

  # ssh into host if a valid IP address is present
  if [ `echo "$IPADDR" |grep -E '^([0-9]+[.]){3}([0-9]+)$'` ]; then
    echo "== $HNAME ($IPADDR) =="
    ssh -t ubuntu@$IPADDR 'echo "== $(hostname) =="; sudo apt-get update; echo "== $(hostname) =="; sudo apt-get upgrade;'
  else
    :
  fi

  INDEX=$((INDEX + 1))
done

This script makes use of BASH arrays. In addition it was discovered that attempting to change variables inside a read loop that was piped was fruitless because the pipe was run inside its own shell and thus the variable changes were lost.

Rust/Cargo Outputting Dollar Angle Bracket Symbols

So you’re compiling source using Cargo/Rust and you see output similar to the following:

error[E0046]$<2>: not all trait items implemented, missing: `decode`, `encode`$<2>
  $<2>--> $<2>src/main.rs:7:1$<2>
   $<2>|$<2>
7$<2>  $<2>| $<2>impl tokio_core::io::Codec for LineCodec {$<2>
   $<2>| $<2>^$<2> missing `decode`, `encode` in implementation$<2>

error$: aborting due to previous error$

This is full of dollar, angle bracket, number, angle bracket characters.

The fix is to change your terminal, e.g.:

TERM=xterm

On doing this I had the output in full colour without the mysterious symbols.

Conditionally adding a directory to PATH if not already in PATH

In BASH it is possible to check that a directory is not already in the PATH environment variable before adding it.

The basic string-in-string search function was adopted from this forum post.

stringContains() { [ -z "${1##*$2*}" ]; }
stringBegins()   { [ -z "${1##$2*}"  ]; }
stringEnds()     { [ -z "${1##*$2}"  ]; }

# call as inPath "$PATH" "/my/new/path"
inPath() {
  if stringBegins   "$1" "$2:";  then return 0; fi
  if stringEnds     "$1" ":$2";  then return 0; fi
  if stringContains "$1" ":$2:"; then return 0; fi
  if [ "$1" == "$2" ];           then return 0; fi
  return 1;
}

If you wanted to, say, add “/home/myuser/bin” if it didn’t already exist, you could add the above functions and the following:

if inPath "$PATH" "/home/myuser/bin"; then
  : # do nothing
else
  PATH="$PATH:/home/myuser/bin"
fi