Words about things. Introverted, Geeky, DevOps.

Getting Started with Badgy


So, I bought a Badgy! It seemed like something interesting and fun, vaguely related to my previous projects converting old Kindle screens into low-power ToDo lists, and I'd have something amusing to take to at least one convention and geek out about.

I had a few issues getting the examples to work, so I've written down the "get started from a complete beginner's perspective" to help anyone else who recently managed to get in on the Tindie order.

This post will go through modifying the provided example to show your name.

Badgy Itself

Default Badgy in a box

Badgy comes without any kind of case or power supply. That's fine - they actually provide a design to use to 3d print a case, or you can use one of the ones other folk have made and published. Or, I dunno, whittle one. I'm looking to make one to attach to a lanyard, so watch this space.

Setting up your Environment

At a minimum, we'll need to install and configure the Arduino IDE (1.8.5+). This is available both from their website as a downloadable installer, and from the Windows store.

Configuring the IDE

We'll now need to install the correct information for the board we're using.

Navigate to File -> Preferences, and add http://arduino.esp8266.com/stable/package_esp8266com_index.json to the Additional Boards Manager URLs field.

Arduino IDE Preferences

Hit OK, and navigate to Tools -> Board "Arduino/Genuine Uno" -> Boards Manager.... We need to add the esp8266 board, so search for and install it.

Go back to the Board "Arduino/Genuine Uno" menu and select the (now installed) NodeMCU 1.0 (ESP-12E Module) option.

We now need to install or otherwise acquire the libraries that the examples require. Navigate to Sketch -> Include Libraries -> Manage Libraries....

The example we're going to use ('hello') uses the following libraries:

  • Adafruit GFX library by Adafruit
  • WiFiManager by tzapu
  • GxEPD by ZinggJM

Annoyingly, GxEPD is not available from the library manager. It is available on GitHub, though the examples currently aren't compatible with the latest version available.

To get the older version, you could download the older version of the repository as a zip file, and then navigate to Sketch -> Include Libraries -> Add .ZIP Library... and provide the downloaded file.

Alternatively, you can use Git to clone the repository into your libraries directory and then reset it to the older commit. Your libraries directory is ~\Documents\Arduino\libraries by default.

cd ~\Documents\Arduino\libraries
git clone https://github.com/ZinggJM/GxEPD.git
cd .\GxEPD
git reset --hard 20eff1d80f7276a3c093183c3823cdd5f6cf1c9d

Modifying the Examples

So, we'll probably need to do something to change this lovely badge to say something else - my name, for example, rather than "Badgy".

If you haven't already done so, download the example files, and open hello.ino in the Arduino IDE.

You can navigate to Sketch -> Verify/Compile to immediately compile this, and the output should be pretty much identical to the hello.bin already contained in the example directory.

Below the block of #include statements, there's a line which defines the name variable. Changing this will result in the showHello() function writing.

/* change this to your name, you may have to adjust font size and cursor location in showHello() to perfectly center the text */
const char* name = "James";

That was easy, hey? Clicking Export compiled Binary on the Sketch menu will generate a bin file in the same directory as hello.ino.

Connecting to Badgy

Now we just need to upload this new bin file to Badgy. The quickest way to do this seems to configure Badgy to connect to your WiFi and upload the bin file.

Configuring WiFi

  1. Make sure Badgy is off (switch on the side closest to the USB connection)
  2. Power Badgy via USB or a CR2450/LIR2450 coin cell
  3. Hold down the center button and slide the power switch to on
  4. When the screen shows that it's ready to connect, release the center button
  5. Connect to the Badgy AP WiFi network
  6. Connect Badgy to your WiFi network
  7. Badgy will now display an IP address!

Uploading

Uploading is simple - now that you have configured Badgy to connect to your network, restarting it whilst holding the center button will put it into update mode, and display the current IP address on the screen.

Navigate to the lightweight upload interface at $IPAddress:8888/update (shown on Badgy's screen) in any browser, and browse for your generated bin file. Click upload, and the Badgy will update itself and automatically reboot.

Congratulations! You should see your name on the screen!

Lazy Random Password Generation in PowerShell


So, good passwords are pretty fashionable these days - with an eye towards the ever-relevant XKCD.

XKCD: Password Strength

Sometimes you just want a short method to generate a random string, with some control over which characters are used.

Here's a method I use when I need to do this with no external requirements:

-join( 33..126  | Get-Random -Count 16 | %{ [char]$_ } )

NB: I'm embarrassed to say I took a variant of this from somewhere a while ago (maybe someone showing something in Slack), and can't remember where from. If anyone recognises this, please let me know and I'll attribute.

Pretty simple (if you ignore the short-form)!

This breaks down to an array of numbers (33..126), which is piped to Get-Random to select a given amount (-Count 16), each of which (or ForEach-Object of which, I guess) is then cast to a [char]. This entire set of chars is then wrapped in a -join(), converting it to a single string with no separator character.

You can, of course, be much more specific with which characters you want to allow by specifying a combination of the following number-ranges:

Value(s) Characters
33..47 !"#$%'()*+,-./
48..57 0-9
58..64 :;<=>?@
65..90 A-Z
91..96 [\]^_`
97..122 a-z
126 ~

So, for mixed-case alphanumeric you could use 48..57 + 65..90 + 97..122

We can quickly and easily display the value-to-character mapping by running something like this:

33..126 | %{[PSCustomObject]@{Number = $_; Value = [char]$_}} | Out-GridView

Examples:

# Create PSCredential
$Credential = [PSCredential]::New(
    "TestUser",
    (-join( 48..57 + 65..90 + 97..122 | Get-Random -Count 16 | %{ [char]$_ } ) | ConvertTo-SecureString -AsPlainText -Force)
)


Sunday Ride with EP


I had a lovely ride with my friend Elaine today, though we got a bit lost.
Seems like Strava crashed at some point, as Elaine tracked us as having gone 45 miles, not ~42.

Bottles of Beer


function Write-BottlesOfBeer {
    param(
        [int]$bottles = 99
    )
    $b='bottles of beer'
    $w='on the wall'
    $n='no more'

    $bottles..1 | %{  
        "$_ $b $w, $_ $b.`r".Replace('bottles',"bottle$(if($_ -gt 1){'s'})")
        "Take one down and pass it around, $(if(($_-1) -ge 1){$_-1} else {$n}) $b $w.`r".Replace('bottles',"bottle$(if($_ -ne 2){'s'})")
        if ($_ -eq 1) {
            "No more $b $w, $n $b. `r"
            "Go to the store and buy some more, 99 $b $w."
        }
    }
}

I thought I'd have a go at a PowerShell version of 99 Bottles of Beer, to make something slightly nicer for 99-bottles-of-beer.net.

Slightly lazy code-golf-esque approach, but I think it's a little nicer than the version currently up for PowerShell.

I also wrote Pester testing for it, for the heck of it, as I've been feeling bad about how badly I wrote some tests for a recent technical evaluation. Sigh.

. ($PSCommandPath -replace '\.tests\.ps1$', '.ps1')

$lyrics = (Invoke-WebRequest 'http://www.99-bottles-of-beer.net/lyrics.html').ParsedHtml.getElementById('main').OuterText.Split("`n") | Select-Object -Skip 1

Describe 'Write-BottlesOfBeer' {
  Context 'Running without arguments'   {
    It 'runs without errors' {
      { Write-BottlesOfBeer } | Should Not Throw
    }
    It 'gets it right' {
        $i = 0
        Write-BottlesOfBeer | %{
            $_ | Should Be $lyrics[$i]
            $i++
        }
    }
  }
}

Passing Tests after the last silly non-compliance

Amazon Echo - CuSteam Skill


After a few days tussling with the out of date walkthroughs Amazon provide for developing Alexa Skills, I was happy to get this working:

...now I've just got to get something useful to other people working.

Export-HashTable


After seeing this amazing thread, I had a quick think about the problem mentioned here.

I wanted [to export] PowerShell Hashtables [to] the file [...] instead of JSON.
The reason this came up was that I was pulling configuration hashtables out of scripts and plac[ing] them into files. This worked when I was editing them by hand and just importing them in my scripts, but then I wanted to generate these config files or make updates to them and could not do it in any intuitive way.

This user wanted to use Hashtable formatting when exporting to files, instead of standard JSON (which has a pre-installed cmdlet). Like this:

@{ 
    HostName    = "Server"
    OS          = "Windows Server 2012 R2 Standard (x64)"
    Environment = "VMWorkstation"
    Role        = "BaseSystem"
}

Instead of this:

{
    "Environment" : "VMWorkstation",
    "HostName"    : "Server",
    "OS"          : "Windows Server 2012 R2 Standard (x64)",
    "Role"        : "BaseSystem"
}

I had a quick attempt at a function to do this. Turns out that you can do it very quickly, but it gets more interesting if you want to format it nicely.

function Export-HashTable {
    param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [Alias('InputObject')]
        [PSObject]$HashTable,
        [String]$Path,
        [String]$Name
    )
    process {
        $divWidth = ($HashTable.Keys.Length | Measure-Object -Maximum).Maximum + 8

        $hashString = ("$(if($Name){"`$$Name = "})@" + ($HashTable | ConvertTo-JSON) -replace '":','"=' -replace '",
','"
').Split("`n") | %{if($_ -match '^(\W+"\w+")=') {
            $_ -replace '^(\W+"\w+")=',"$($Matches[1] + (' '*($divWidth - $Matches[0].Length)))="}
            else {$_}
        }
    }
    end {
        if ($Path) {
            Out-File -FilePath $path -InputObject $hashString -Encoding utf8
        }
        else {
            return $hashString
        }
    }
}

</script src="https://gist.github.com/JPRuskin/e24bd5c6d2a7c95fca50f4a7d7755497.js">

An up to date copy can be found here.

Vulfpeck Live!


Holy heck, these guys are simply amazing. Here's their bandcamp. Their albums are great, but their live performances are just... something else.

I saw them live on Saturday 17th, at the Brooklyn Bowl at the Millenium Dome (O2 something?), and it was absolutely wonderful.

I've been enjoying the live shows posted on YouTube (though a few have now been taken down, sad face). As an aside: I totally understand if someone wants to take down a video of something because they're selling it - but it turns out I can't buy the stream anymore, because they only sold it prior to the event. Hmph.

Anyway, these guys rock, and I could go on for hours about how much I think so.

SysInternals Updater


I was just running a delightful batch script written by Jason Faulkner to update my SysInternals tools directory.

Sysinternals Updater

It was taking forever! At least 2 seconds per tool (regardless of state), and a huge chunk of time to download the current tool list. It seems to download everything, regardless of whether or not it's up-to-date.

I thought it may be far quicker in PowerShell with some more up to date methods, so I sketched this out. It's far less robust (won't attempt to kill processes if they're in use), as I actually don't want that functionality.

function Update-SysInternalsTools {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        $localDirectory
    )
    begin {
        $localTools  = Get-ChildItem $localDirectory -Filter *.exe
        $onlineTools = Get-ChildItem '\\live.sysinternals.com\tools' -Filter *.exe
    }
    process {
        foreach ($tool in $localTools) {
            if ($tool.LastWriteTime -lt ($online = $onlineTools | ?{$_.Name -eq $tool.Name}).LastWriteTime) {
                Write-Verbose "[$($tool.Name)] requires updating..."
                try {
                    Invoke-WebRequest -Uri $online.FullName -OutFile $tool.FullName -ErrorAction Stop
                }
                catch {
                    Write-Warning "$($tool.Name) failed to update."
                }
            }
        }
    }
}

if ($MyInvocation.InvocationName -NE '.') {Update-SysInternalsTools}

I'll probably update the script shortly with kill-process-and-restart as an option (as well as a "download everything" argument, as it currently only updates tools that are present), but it seems to get through the listing far faster.

It's also currently just comparing LastWriteTime - it could actually grab the VersionInfo/FileVersion with Get-ItemProperty, but I thought that might add a very large chunk of (fairly unnecessary) delay. Will have to test it.

Google Chrome allows Tab-by-Tab muting


Was just going to re-enable NPAPI plugins in Chrome, and I found this:

chromeflags-tabaudiomute

Now that's cool! Slightly weirdly, I love exploring the about:flags page in Chrome (Canary, or otherwise) and finding things in various stages of development - and I always loved the functionality that allowed you to see which tab was playing audio.

On hover:
chromeflags-tabaudiomute-mouseover
Context Menu:
chromeflags-tabaudiomute-contextmenu

Active Directory - Storing Bitlocker Recovery Info


A recent quick project was to enable storage of Bitlocker recovery data within Active Directory, instead of our moderately secure encrypted drive of text-files.

This is actually a really easy process (assuming you only have Windows 7 / 2008R2 and up on the domain), only needing to make a few adjustments to ACLs on ADComputer objects (allowing Computers to write to their own objects).

The Technet article describing this, along with the more convoluted method involved in sorting this out for anything below 2008R2 is here: Backing Up BitLocker and TPM Recovery Information to AD DS

Unfortunately, it's a bit of a hassle (very minor hassle, anyway) to load up ADSIEdit.msc, and navigate around to the correct object every time you want to retrieve a key... so I wrote a quick Powershell script to replace the VBScript linked in the above article.

param(
    [Alias("Computer","Name")][string[]]$Computers,
    [Parameter(Mandatory=$true)][PSCredential]$Credential
)

function Get-BitlockerRecovery($Computers, $Credential) {
    $report = @()

    foreach ($Computer in $Computers) {
    $objects = Get-ADObject -Filter * -SearchBase (Get-ADComputer $Computer).DistinguishedName -Credential $Credential -Properties * | Where -Property ObjectClass -eq msFVE-RecoveryInformation

    foreach ($key in $objects) {
        $keyInfo = "" | Select Computer, RecoveryID, RecoveryPassword
        $keyInfo.Computer = $Computer
        $key.Name -match ".*\{(.*)\}" | Out-Null
        $keyInfo.RecoveryID = $matches[1]
        $keyInfo.RecoveryPassword = $key."msFVE-RecoveryPassword"

        $report += $keyInfo
        }
    }
    return $report
}


Get-BitlockerRecovery -Computers $Computers -Credential $Credential

I'm sure there's probably a nicer way to do it, and that this could be compressed down to three lines or so - but I think that's quite legible.