Words about things. Introverted, Geeky, DevOps.

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

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.

PoSH Fun


I was browsing r/Powershell (as you do), and noticed a chap complaining about the test at the end of a course, where he was asked to

Create a directory named "newdir" in every empty directory whose name is longer than 8 characters recursively.

His code went like this:

$currentdir=pwd;
$array=@()
get-childitem -recurse | where{$_.Mode -like "d*"} | foreach-object         {$array+=$_.FullName;}
$size=$array.Length;
for($i=0; $i -lt $size; i++)
{
if($array[$i].Length -gt 8)
{
cd $array[$i];
mkdir newdir;
}
}
cd $currentdir;
echo "Done"    

I've been pushing to go on a decent Powershell course for a while, but I think I'll need to pick another course, as I'm sure I can do something more PS specific. Let's have a go...

Though I did miss one requirement (only create files in empty directories), I began with this:

$targetdir = C:\Users\James\Work\Powershell\
$dirs = @()

gci $targetdir -Recurse -Directory | %{ 
	if(($_.Name).Length -ge 8){$dirs += $_.fullname}
}
$dirs | %{ mkdir (Join-Path $dir "newdir")}

Of course, changing that last line to a quick check solves the empty folders issue -

foreach ($dir in $dirs) {
	if (!(gci $dir)) {mkdir (Join-Path $dir "newdir")}
}

I do realise that I'm cheating a bit - apparently he was only using stuff available in Powershell 2.0 (poor chap), so the -Directory argument for GCI was unavailable.

Please also note, @stuidge, that Powershell is capable of more fun things, but I'm lazy (and I'd written this out for a reddit post last night... then not posted it, then rewritten it briefly for this).