Words about things. Introverted, Geeky, DevOps.

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)
)


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).