Thursday, April 18, 2024
HomePowershellPorting System.Net.Safety.Membership.GeneratePassword() to PowerShell - PowerShell Group

Porting System.Net.Safety.Membership.GeneratePassword() to PowerShell – PowerShell Group


I’ve been utilizing PowerShell (core) for a few years now, and it grew to become pure to create automations with all of the options that aren’t current in Home windows PowerShell. Nonetheless, there may be nonetheless one characteristic I miss in PowerShell, and this characteristic, for as foolish because it sounds, is the GeneratePassword, from System.Net.Safety.Membership.

This occurs as a result of this meeting was developed in .NET Framework, and never dropped at .NET (core). Though there are a number of options to realize the identical end result, I assumed that is the right alternative to point out the Energy in PowerShell, and port this methodology from C#.

Technique

We’re going to get this methodology’s code by utilizing an IL decompiler. C# is compiled to an Intermediate Language, which permits us to decompile it. The instrument I’ll be utilizing is ILSpy, and could be discovered on the Microsoft Retailer.

DISCLAIMER: The code for GeneratePassword and the System.Net library weren’t written by me, and the aim of decompiling it’s purely instructional. For as innocent as this code is, it doesn’t have any safety warranties, neither is meant for misuse.

Getting the Code

As soon as put in, open ILSpy, click on on File and Open from GAC….

On the search bar, kind System.Net, choose the meeting, and click on Open.

Open from GAC menu

As soon as loaded, increase the System.Net meeting tree, and the System.Net.Safety namespace. Inside System.Net.Safety, search for the Membership class, click on on it, and the decompiled code ought to seem on the appropriate pane.

Membership class

Scroll down till you discover the GeneratePassword methodology, and increase it.

GeneratePassword method

Porting to PowerShell

Now the enjoyable begins. Let’s do that utilizing PowerShell instruments solely, means we’re not going to repeat the Membership class and methodology. We’re going to create a operate, and hold the variable names the identical, so it’s simpler for us to match.

  • Beginning with the tactic’s signature: public static string GeneratePassword(int lenght, int numberOfNonAlphanumericCharacters)
    • public means this methodology could be referred to as from outdoors the meeting.
    • static means I can name this methodology with out having to instantiate an object of kind Membership.
    • string means this methodology returns a string.
  • Utility strategies and properties. GeneratePassword makes use of strategies and properties which might be additionally outlined within the System.Net library.
    • Strategies
      • System.Net.CrossSiteScriptingValidation.IsDangerousString(string s, out int matchIndex)
      • System.Net.CrossSiteScriptingValidation.IsAtoZ(char c)
    • Properties
      • char[] punctuations, from System.Net.Safety.Membership
      • char[] startingChars, from System.Net.CrossSiteScriptingValidation

Now sufficient C#, let get to scripting.

Foremost operate

For this, we’re going to use the Superior Perform template, from Visible Studio Code. I’ll title the primary operate New-StrongPassword, however you possibly can title it as you want, simply bear in mind utilizing accepted verbs.

This methodology takes as parameter two integer numbers, let’s create them within the param() block. The primary two if statements are checks to make sure each parameters are inside acceptable vary. We will accomplish the identical with parameter attributes.

operate New-StrongPassword {

    [CmdletBinding()]
    param (

        # Variety of characters.
        [Parameter(
            Mandatory,
            Position = 0,
            HelpMessage="The number of characters the password should have."
        )]
        [ValidateRange(1, 128)]
        [int] $Size,

        # Variety of non alpha-numeric chars.
        [Parameter(
            Mandatory,
            Position = 1,
            HelpMessage="The number of non alpha-numeric characters the password should contain."
        )]
        [ValidateScript({
            if ($PSItem -gt $Length -or $PSItem -lt 0) {
                $newObjectSplat = @{
                    TypeName="System.ArgumentException"
                    ArgumentList="Membership minimum required non alpha-numeric characters is incorrect"
                }
                throw New-Object @newObjectSplat
            }
            return $true
        })]
        [int] $NumberOfNonAlphaNumericCharacters

    )

    start {

    }

    course of {

    }

    finish {

    }
}

Utilities

Now let’s concentrate on the Start{} block, and create these utility strategies, and properties.

Properties

These are the 2 properties, in our case variables, that we have to create.

personal static char[] startingChars = new char[2] { '<', '&' };
personal static char[] punctuations = "!@#$%^&*()_-+=[{]};:>|./?".ToCharArray();

Let’s create them as international variables, for use throughout our features if mandatory.

[char[]]$international:punctuations = @('!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_',
                                 '-', '+', '=', '[', '{', ']', '}', ';', ':', '>', '|',
                                 '.', '/', '?')
[char[]]$international:startingChars = @('<', '&')

Get-IsAtoZ

That is what the tactic appears like:

personal static bool IsAtoZ(char c)
{
    if (c < 'a' || c > 'z')
    {
        if (c >= 'A')
        {
            return c <= 'Z';
        }
        return false;
    }
    return true;
}

Fairly easy methodology, with one parameter, solely the operator’s title wants to alter. Let’s use an inline operate:

operate Get-IsAToZ([char]$c) {
    if ($c -lt 'a' -or $c -gt 'z') {
        if ($c -ge 'A') {
            return $c -le 'Z'
        }
        return $false
    }
    return $true
}

Get-IsDangerousString

That is what the C# methodology appears like:

inside static bool IsDangerousString(string s, out int matchIndex)
{
    matchIndex = 0;
    int startIndex = 0;
    whereas (true)
    {
        int num = s.IndexOfAny(startingChars, startIndex);
        if (num < 0)
        {
            return false;
        }
        if (num == s.Size - 1)
        {
            break;
        }
        matchIndex = num;
        change (s[num])
        {
        case '<':
            if (IsAtoZ(s[num + 1]) || s[num + 1] == '!' || s[num + 1] == '/' || s[num + 1] == '?')
            {
                return true;
            }
            break;
        case '&':
            if (s[num + 1] == '#')
            {
                return true;
            }
            break;
        }
        startIndex = num + 1;
    }
    return false;
}

This one is a bit more in depth, but it surely’s just about solely string manipulation. The fascinating a part of this methodology although, is the parameter matchIndex. Be aware the out key phrase, this implies this parameter is handed as reference. We may skip this parameter altogether, as a result of just isn’t utilized in our case, however it is a good alternative to train the PSReference kind.

operate Get-IsDangerousString {

    param([string]$s, [ref]$matchIndex)

    # To entry the referenced parameter's worth, we use the 'Worth' property from PSReference.
    $matchIndex.Worth = 0
    $startIndex = 0

    whereas ($true) {
        $num = $s.IndexOfAny($international:startingChars, $startIndex)
        if ($num -lt 0) {
            return $false
        }
        if ($num -eq $s.Size - 1) {
            break
        }
        $matchIndex.Worth = $num

        change ($s[$num]) {
            '<' {
                if (
                    (Get-IsAToZ($s[$num + 1])) -or
                    ($s[$num + 1] -eq '!')     -or
                    ($s[$num + 1] -eq '/')     -or
                    ($s[$num + 1] -eq '?')
                ) {
                    return $true
                }
            }
            '&' {
                if ($s[$num + 1] -eq '#') {
                    return $true
                }
            }
        }
        $startIndex = $num + 1
    }
    return $false
}

With these, our Start{} block appears like this:

Start {
    [char[]]$international:punctuations = @('!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_',
                                     '-', '+', '=', '[', '{', ']', '}', ';', ':', '>', '|',
                                     '.', '/', '?')
    [char[]]$international:startingChars = @('<', '&')

    operate Get-IsAToZ([char]$c) {
        if ($c -lt 'a' -or $c -gt 'z') {
            if ($c -ge 'A') {
                return $c -le 'Z'
            }
            return $false
        }
        return $true
    }

    operate Get-IsDangerousString {

        param([string]$s, [ref]$matchIndex)

        $matchIndex.Worth = 0
        $startIndex = 0

        whereas ($true) {
            $num = $s.IndexOfAny($international:startingChars, $startIndex)
            if ($num -lt 0) {
                return $false
            }
            if ($num -eq $s.Size - 1) {
                break
            }
            $matchIndex.Worth = $num

            change ($s[$num]) {
                '<' {
                    if (
                        (Get-IsAToZ($s[$num + 1])) -or
                        ($s[$num + 1] -eq '!')     -or
                        ($s[$num + 1] -eq '/')     -or
                        ($s[$num + 1] -eq '?')
                    ) {
                        return $true
                    }
                }
                '&' {
                    if ($s[$num + 1] -eq '#') {
                        return $true
                    }
                }
            }
            $startIndex = $num + 1
        }
        return $false
    }
}

Foremost Perform Physique

On this stage we construct the operate itself. Since we’re utilizing attributes to test the parameters, the primary two if statements are ignored. After that, we’ve got a single do-while loop. On this loop, we’re going to use instruments from the System.Safety.Cryptography library, so let’s import it.

Add-Sort -AssemblyName System.Safety.Cryptography

# If you happen to get 'Meeting can't be discovered' errors, load it with partial title as an alternative.
[void][System.Reflection.Assembly]::LoadWithPartialName('System.Safety.Cryptography')

First let’s declare the variables utilized in the primary operate physique, and inside the primary loop. This provides us the chance to investigate our selections.

# Explicitly declaring the output 'textual content' to match the tactic. We will skip this delaration.
# Similar for the 'matchIndex'
$textual content = [string]::Empty
$matchIndex = 0
do {
    $array = New-Object -TypeName 'System.Byte[]' -ArgumentList $Size
    $array2 = New-Object -TypeName 'System.Char[]' -ArgumentList $Size
    $num = 0

    # This stage could possibly be performed in 3 methods. We may use 'New-Object' and imediately name
    # 'GetBytes' on it, we may use the category constructor immediately, and name 'GetBytes'
    # on it: [System.Security.Cryptography.RNGCryptoServiceProvider]::new().GetBytes(),
    # or we may instantiate the 'RNGCryptoServiceProvider' object utilizing one of many
    # earlier strategies, and name 'GetBytes' on it. Since we're utilizing PowerShell instruments the
    # most we will, and we need to keep true to the tactic, let's use the primary possibility.
    # [void] used to suppress output.
    [void](New-Object -TypeName 'System.Safety.Cryptography.RNGCryptoServiceProvider').GetBytes($array)

    # Be aware that when passing a variable as reference to a operate parameter, we have to
    # solid it to 'PSReference'. The parentheses are mandatory so the parameter makes use of the
    # object, and never use it as a string.
} whereas ((Get-IsDangerousString -s $textual content -matchIndex ([ref]$matchIndex)))

Be aware that in our pursuit to remain true to the tactic’s structure, we’re together with additional declarations. Though this could possibly be prevented, in some circumstances it helps with script readability. Plus, when you have expertise with any programming language, it will really feel acquainted.

Proper after that, we’ve got a for loop, which is able to select every character for our password. It does this with a sequence of mathematical operations, and comparisons.

for ($i = 0; $i -lt $Size; $i++) {
    $num2 = [int]$array[$i] % 87
    if ($num2 -lt 10) {
        $array2[$i] = [char](48 + $num2)
        proceed
    }
    if ($num2 -lt 36) {
        $array2[$i] = [char](65 + $num2 - 10)
        proceed
    }
    if ($num2 -lt 62) {
        $array2[$i] = [char](97 + $num2 - 36)
        proceed
    }
    $array2[$i] = $international:punctuations[$num2 - 62]
    $num++
}

The subsequent session goes to handle our variety of non-alphanumeric characters. It does that by producing random image characters and changing values within the array we stuffed within the earlier loop.

if ($num -lt $NumberOfNonAlphaNumericCharacters) {
    $random = New-Object -TypeName 'System.Random'

    # Producing solely the characters left to finish our parameter specification.
    for ($j = 0; $j -lt $NumberOfNonAlphaNumericCharacters - $num; $j++) {
        $num3 = 0
        do {
            $num3 = $random.Subsequent(0, $Size)
        } whereas (![char]::IsLetterOrDigit($array2[$num3]))
        $array2[$num3] = $international:punctuations[$random.Next(0, $global:punctuations.Length)]
    }
}

Now all that’s left is to create a string from the character array, and test if it’s protected with Get-IsDangerousString.

$textual content = [string]::new($array2)

If our textual content is protected, we return it and the operate reaches finish of execution. Our completed operate appears like this:

operate New-StrongPassword {

    [CmdletBinding()]
    param (

        # Variety of characters.
        [Parameter(
            Mandatory,
            Position = 0,
            HelpMessage="The number of characters the password should have."
        )]
        [ValidateRange(1, 128)]
        [int] $Size,

        # Variety of non alpha-numeric chars.
        [Parameter(
            Mandatory,
            Position = 1,
            HelpMessage="The number of non alpha-numeric characters the password should contain."
        )]
        [ValidateScript({
            if ($PSItem -gt $Length -or $PSItem -lt 0) {
                $newObjectSplat = @{
                    TypeName="System.ArgumentException"
                    ArgumentList="Membership minimum required non alpha-numeric characters is incorrect"
                }
                throw New-Object @newObjectSplat
            }
        })]
        [int] $NumberOfNonAlphaNumericCharacters

    )

    Start {
        [char[]]$international:punctuations = @('!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_',
                                         '-', '+', '=', '[', '{', ']', '}', ';', ':', '>', '|',
                                         '.', '/', '?')
        [char[]]$international:startingChars = @('<', '&')

        operate Get-IsAToZ([char]$c) {
            if ($c -lt 'a' -or $c -gt 'z') {
                if ($c -ge 'A') {
                    return $c -le 'Z'
                }
                return $false
            }
            return $true
        }

        operate Get-IsDangerousString {

            param([string]$s, [ref]$matchIndex)

            $matchIndex.Worth = 0
            $startIndex = 0

            whereas ($true) {
                $num = $s.IndexOfAny($international:startingChars, $startIndex)
                if ($num -lt 0) {
                    return $false
                }
                if ($num -eq $s.Size - 1) {
                    break
                }
                $matchIndex.Worth = $num

                change ($s[$num]) {
                    '<' {
                        if (
                            (Get-IsAToZ($s[$num + 1])) -or
                            ($s[$num + 1] -eq '!')     -or
                            ($s[$num + 1] -eq '/')     -or
                            ($s[$num + 1] -eq '?')
                        ) {
                            return $true
                        }
                    }
                    '&' {
                        if ($s[$num + 1] -eq '#') {
                            return $true
                        }
                    }
                }
                $startIndex = $num + 1
            }
            return $false
        }
    }

    Course of {
        Add-Sort -AssemblyName 'System.Safety.Cryptography'

        $textual content = [string]::Empty
        $matchIndex = 0
        do {
            $array = New-Object -TypeName 'System.Byte[]' -ArgumentList $Size
            $array2 = New-Object -TypeName 'System.Char[]' -ArgumentList $Size
            $num = 0
            [void](New-Object -TypeName 'System.Safety.Cryptography.RNGCryptoServiceProvider').GetBytes($array)

            for ($i = 0; $i -lt $Size; $i++) {
                $num2 = [int]$array[$i] % 87
                if ($num2 -lt 10) {
                    $array2[$i] = [char][07]
                    proceed
                }
                if ($num2 -lt 36) {
                    $array2[$i] = [char][08]
                    proceed
                }
                if ($num2 -lt 62) {
                    $array2[$i] = [char][09]
                    proceed
                }
                $array2[$i] = $international:punctuations[$num2 - 62]
                $num++
            }

            if ($num -lt $NumberOfNonAlphaNumericCharacters) {
                $random = New-Object -TypeName 'System.Random'

                for ($j = 0; $j -lt $NumberOfNonAlphaNumericCharacters - $num; $j++) {
                    $num3 = 0
                    do {
                        $num3 = $random.Subsequent(0, $Size)
                    } whereas (![char]::IsLetterOrDigit($array2[$num3]))
                    $array2[$num3] = $international:punctuations[$random.Next(0, $global:punctuations.Length)]
                }
            }

            $textual content = [string]::new($array2)
        } whereas ((Get-IsDangerousString -s $textual content -matchIndex ([ref]$matchIndex)))
    }

    Finish {
        return $textual content
    }
}

End result

Now all that’s left is to name our operate:

New-StrongPassword

Conclusion

I hope you had as a lot enjoyable as I had constructing this operate. With this new ability, you possibly can enhance your scripts’ complexity and reliability. This additionally makes you extra comfy to put in writing your individual modules, binary or not.

Thanks for going alongside.

Glad scripting!

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments