Convert a password to a phonetic string for end users As much as I hate it, sometimes providing passwords to people has to be done electronically. When I do that I try to remove any ambiguity that might be created by the font, user etc. and provide a phonetic translation of the string. For things like 0 and O or l and I which might not be obvious at first. 

The function below can take a single string or be piped a string array. I could get crazy with this later to support other languages by making some nested arrays but for now it only supports English. 

function Get-PhoneticString{
    param(
        [parameter(ValueFromPipeline=$true, Mandatory=$true)]
        [string]$StringToExplode,
        [string]$Delimiter = " - "
    )
    process{
        # Alphabetic conversions using military phonetics
        $phoneticAlphabet = @{
            "a" = "alfa"; "b" = "bravo"; "c" = "charlie"; "d" = "delta"; "e" = "echo";
            "f" = "foxtrot"; "g" = "golf"; "h" = "hotel"; "i" = "india"; "j" = "juliett";
            "k" = "kilo"; "l" = "lima"; "m" = "mike"; "n" = "november"; "o" = "oscar";
            "p" = "papa"; "q" = "quebec"; "r" = "romeo"; "s" = "sierra"; "t" = "tango";
            "u" = "uniform"; "v" = "victor"; "w" = "whiskey"; "x" = "x-ray"; "y" = "yankee";
            "z" = "zulu"
        }

        $numberAsString = @{
            "0"="zero"; "1"="one"; "2"="two"; "3"="three"; "4"="four"
            "5"="five"; "6"="six"; "7"="seven"; "8"="eight"; "9"="nine"
        }

        $symbolAsString = @{
            "!" = "Exclamation Mark"; '"' = "Double Quote"; "#" = "Number Sign"; "$" = "Dollar"; "%" = "Percent"; "&amp;" = "Ampersand"
            "'" = "Single Quote"; "(" = "Left Parenthesis"; ")" = "Right Parenthesis"; "*" = "Asterisk"; "+" = "Plus"
            "," = "Comma"; "-" = "Minus"; "." = "Period"; "/" = "Slash"; ":" = "Colon"; ";" = "Semicolon"; "&lt;" = "Less Than"
            "=" = "Equality Sign"; "&gt;" = "Greater Than"; "?" = "Question Mark"; "@" = "At Sign"; "[" = "Left Square Bracket"
            "\" = "Backslash"; "]" = "Right Square Bracket"; "^" = "Caret"; "_" = "Underscore"; '`' = "Grave / Accent"
            "{" = "Left Curly Bracket"; "|" = "Vertical Bar"; "}" = "Right Curly Bracket"
        }

        # Convert this string into a character array. For each character find its string equivilent in one of the defined hash tables
        ($StringToExplode.ToCharArray() |  ForEach-Object{
            switch -Regex -CaseSensitive ($_){
                "[a-z]"{$phoneticAlphabet."$_"}
                "[A-Z]"{($phoneticAlphabet."$_").ToUpper()}
                "\d"{$numberAsString."$_"}
                "\s"{$_}# Return whitespace unaltered
                default{ $symbolAsString."$_"}
            }
        }) -join $Delimiter
    }

}


So a sample call and output would look like this

"K0D3R3v13w" | Get-PhoneticString
KILO - zero - DELTA - three - ROMEO - three - victor - one - three - whiskey


It only supports the basic ASCII printable character set. Any uppercase alphabetical characters will have all uppercase strings returned. If the character is not alphanumeric it will be assumed that it is a symbol in the set. If that character is not covered at all then it will show as empty in the resultant string and means something needs to be updated to account for it. Since I am using this for passwords that should cover near all supported characters.
Your code looks pretty good to me. I applaud your using datastructures to hold the mappings. A lot of developers would have used a humongous switch statement there, but I think as a general rule, datastructures are to be preferred over code as they makes things clearer and more flexible.

It's a good programming principle to break things down into smaller functions. That makes the code easier to read and maintain. It makes your code self-documenting, which means you don't need to rely on comments so much. 

We can put the inner logic of your blob of code into its own function:

function wordFromChar($char)
{
    switch -Regex -CaseSensitive ($char){
        "[a-z]"{$phoneticAlphabet."$char"}
        "[A-Z]"{($phoneticAlphabet."$char").ToUpper()}
        "\d"{$numberAsString."$char"}
        "\s"{$char}# Return whitespace unaltered
        default{ $symbolAsString."$char"}
    }
}


And then call it like this:

$words = $StringToExplode.ToCharArray() |  ForEach-Object {
    wordFromChar $_
}

$words -join $Delimiter


We can improve the code in the wordFromChar function. The regular expressions such as [a-z] are repeating the information that is already contained in the hash table. That violates the DRY Principle. ("Don't repeat yourself.") This is an important principle which you can look up.

I'd be inclined to put all the hash tables into a single hash table. I don't see why there should be separate tables. Having separate tables just means we have more stuff to deal with but without any real benefits. Assuming that we make a single hash table called $charMap, we could rewrite the function like this:

function wordFromChar($char)
{
    if ($charMap.Contains($char))
    {
        $result = $charMap[$char]
    }
    else 
    {
        throw "Can't handle '$char'"    
    }

    if (isUppercase $char)
    {
        $result = $result.ToUpper()
    }

    $result
}


(If there was a good reason to keep the separate hash tables, we could certainly do that. We could just put them all in an array. That is, we would have an array of hash tables. That would make the lookup logic a little more complicated, but only by a little.)

isUppercase is a new function that could look like this:

function isUppercaseChar($char)
{
    $char -cmatch "^[A-Z]$"
}


-cmatch is a case-sensitive regular expression match.

I haven't tested any of the code above, by the way.