Your basic ITPro blog... What's going on at work, what I'm interested in.

Tuesday, October 28, 2008

Randomness... Is it possible?

I was looking at creating randomness again and came across: RNGCryptoServiceProvider

Specifically, System.Security.Cryptography.RNGCryptoServiceProvider

Most of what I read said that this is the best way to generate random values.

From what I can tell, it is designed to generate a random byte. So, it will fill a byte array with random values between 1 and 255. This is the behavior I saw when testing in PowerShell.

Most of the examples I found online of how to use this to generate a random selection out of an array of options is to do something like:

----------------------------------------------------

# Create character array
$chars = "qwertyuiopasdfghjklzxcvbnm1234567890"

# Create a byte array with one place in it
$byte = New-Object System.Byte[] 1

# Create random number generator
$rng = New-Object System.Security.Cryptography.RNGCryptoServiceProvider

# Fill our byte array with a random value (between 1 and 255), giving us a single random byte value
$rng.GetBytes($byte)

# Get and integer of this value
$rnd = $byte[0] -as [int]

# Generate an integer to be used as an index value in our character array
$int = ($rnd % $($chars.Length))

# Get your random character
$chars[$int]

----------------------------------------------------

While this works fine, I got to wondering if this method will favor some characters in our character array over others. For example, look at this output. I am executing the command "1..255 | % {$_ % $chars.Length} | group" which...

- counts from 1 to 255, sending each number down the pipeline
- for each number, it calcs <number> MODULO <CharArrayLength>
- it then groups my results

*************

§ Derek-D620 {C:\s\u} 1..255 | % {$_ % $chars.Length} | group

Count Name                      Group
----- ----                      -----
    8 1                         {1, 1, 1, 1...}
    8 2                         {2, 2, 2, 2...}
    8 3                         {3, 3, 3, 3...}
    7 4                         {4, 4, 4, 4...}
    7 5                         {5, 5, 5, 5...}
    7 6                         {6, 6, 6, 6...}
    7 7                         {7, 7, 7, 7...}
    7 8                         {8, 8, 8, 8...}
    7 9                         {9, 9, 9, 9...}
    7 10                        {10, 10, 10, 10...}
    7 11                        {11, 11, 11, 11...}
    7 12                        {12, 12, 12, 12...}
    7 13                        {13, 13, 13, 13...}
    7 14                        {14, 14, 14, 14...}
    7 15                        {15, 15, 15, 15...}
    7 16                        {16, 16, 16, 16...}
    7 17                        {17, 17, 17, 17...}
    7 18                        {18, 18, 18, 18...}
    7 19                        {19, 19, 19, 19...}
    7 20                        {20, 20, 20, 20...}
    7 21                        {21, 21, 21, 21...}
    7 22                        {22, 22, 22, 22...}
    7 23                        {23, 23, 23, 23...}
    7 24                        {24, 24, 24, 24...}
    7 25                        {25, 25, 25, 25...}
    7 26                        {26, 26, 26, 26...}
    7 27                        {27, 27, 27, 27...}
    7 28                        {28, 28, 28, 28...}
    7 29                        {29, 29, 29, 29...}
    7 30                        {30, 30, 30, 30...}
    7 31                        {31, 31, 31, 31...}
    7 32                        {32, 32, 32, 32...}
    7 33                        {33, 33, 33, 33...}
    7 34                        {34, 34, 34, 34...}
    7 35                        {35, 35, 35, 35...}
    7 0                         {0, 0, 0, 0...}

*****************

As you can see, some results come up more than others. Specifically (in this case) 1, 2, and 3 will show up more often than the rest of the results. So, looking at my CharArray, the letters w, e, and r will show up more often in my result sets. I am thinking that, to create an environment that does not favor any particular subset of our character array, I need my 'byte' value to be evenly divisible by my charArray length. So, rather than using a max possible value of 255, my max possible value needs to be the largest value where "maxValue MODULO charArrayLength = 0"

Ugh!

And, this doesn’t take into account the possibility that my max value could be larger than 255, in which case this formula just spits out 255.

Ugh! Ugh!

Thursday, October 23, 2008

Generating Passwords with Powershell

UPDATES AT BOTTOM OF POST!

I wanted a way to generate random passwords using Powershell. Research for this was a lot of fun, and I ran across some great resources.

--------------------------------------------

http://dmitrysotnikov.wordpress.com/2007/07/18/generate-random-password-with-powershell/ 
- Love the comment referring to using ”System.Web”

http://www.peterprovost.org/blog/post/Quick-n-Dirty-PowerShell-Password-Generator.aspx

http://www.hanselman.com/blog/DictionaryPasswordGeneratorInPowershell.aspx 
- Interesting variation, while promoting passphrases over passwords (a great discussion to have, no doubt!)

http://www.terminal23.net/2007/06/powershell_random_password_gen.html 
- Cool solution to this

http://www.leadfollowmove.com/archives/powershell/powershell-less-code-same-result

--------------------------------------------

I wanted a couple of specifics regarding my tool's functionality.

  • The option of including/excluding subsets of characters (uppercase, lowercase, numbers, special characters)
  • The option of setting the password length
  • The results had to include at least one character from each subset desired
  • It had to be easily usable in the pipeline

With that all in mind, I came up with the following function. This is the format that I have settled on (more or less) for my functions. I don't know if it's the best way to do things (probably not), but it seems to be working for me at this time.

I am glad to be able to add my $0.02 to the conversation.

#######################################################################
# FUNCTION NAME: New-Password
#   
# See USAGE() function for docs.
#
# WRITTEN BY: Derek Mangrum
#
# REVISION HISTORY:
#     2008-10-23 : Initial version
#######################################################################
function New-Password
{
    param 
    (
        [int]$length,
        [switch]$lowerCase,
        [switch]$upperCase,
        [switch]$numbers,
        [switch]$specialChars
    )

    BEGIN
    {
        # Usage Instructions
        function Usage() 
        {
            Write-Host ''
            Write-Host 'FUNCTION NAME: New-Password' -ForegroundColor White
            Write-Host ''
            Write-Host 'USAGE'
            Write-Host '    New-Password -length 10 -upperCase -lowerCase -numbers'
            Write-Host '    New-Password -length 10 -specialChars'
            Write-Host '    New-Password -le 10 -lo -u -n -s'
            Write-Host '    New-Password'
            Write-Host ''
            Write-Host 'DESCRIPTION:'
            Write-Host ' Generates a random password of a given length (-length parameter)'
            Write-Host ' comprised of at least one character from each subset provided'
            Write-Host ' as a switch parameter.'
            Write-Host ''
            Write-Host 'AVAILABLE SWITCHES:'
            Write-Host ' -lowerCase    : include all lower case letters'
            Write-Host ' -upperCase    : include all upper case letters'
            Write-Host ' -numbers      : include 0-9'
            Write-Host ' -specialChars : include the following- !@#$%^&*()_+-={}[]<>'
            Write-Host ''
            Write-Host 'REQUIREMENTS:'
            Write-Host ' You must provide the -length (four or greater) and at least one character switch'
            Write-Host ''
        }
        
        function generate_password
        {
            if ($lowerCase)    
            { 
                $charsToUse += $lCase
                $regexExp += "(?=.*[$lCase])"
            }
            if ($upperCase)        
            { 
                $charsToUse += $uCase 
                $regexExp += "(?=.*[$uCase])"
            }
            if ($numbers)
            { 
                $charsToUse += $nums 
                $regexExp += "(?=.*[$nums])"
            }
            if ($specialChars)    
            { 
                $charsToUse += $specChars
                $regexExp += "(?=.*[\W])"
            }
            
            $test = [regex]$regexExp
            $rnd = New-Object System.Random
            
            do 
            {
                $pw = $null
                for ($i = 0 ; $i -lt $length ; $i++)
                {
                    $pw += $charsToUse[($rnd.Next(0,$charsToUse.Length))]
                    Start-Sleep -milliseconds 20
                }
            }
            until ($pw -match $test)
            
            return $pw
        }

        # Displays help
        if (($Args[0] -eq "-?") -or ($Args[0] -eq "-help")) 
        {
            Usage
            break
        }
        else
        {
            $lCase = 'abcdefghijklmnopqrstuvwxyz'
            $uCase = $lCase.ToUpper()
            $nums = '1234567890'
            $specChars = '!@#$%^&*()_+-={}[]<>'
        }
    }
    
    PROCESS
    {
        if (($length -ge 4) -and ($lowerCase -or $upperCase -or $numbers -or $specialChars))
        {
            $newPassword = generate_password
        }
        else
        {
            Usage
            break
        }
        
        $newPassword
    }
    
    END
    {
    }
}

UPDATE:
I was talking with friends about this script and something about it I did not like. Namely, the need I had to pause briefly in my password-generation loop (the 'Start-Sleep -m 20'). They said that I needed to seed my System.Random object. Hmmm... OK. Then we talked about how to create a good seed value for this. Long story short, we landed on the following:

$seed = ([system.Guid]::NewGuid().GetHashCode())
$rnd = New-Object System.Random ($seed)

We create a $seed by calling the NewGuid().GetHashCode() method of System.Guid. Then we use this $seed to create our $rnd object. Also, we can then take out the pause. Passwords, as you can imagine, generate much more quickly now.

Wednesday, October 15, 2008

A Quick Quiz...

QUESTION:  What just increased from 1.5Mb to 200Mb?

ANSWER:  The point-to-point link between our two campuses!

We completed the initial installation of our Qwest QMOE line between our campuses. This link is replacing a T1, upping our bandwidth by over 100 times. The T1 is still in place and, using OSPF to handle routing, is automatically used if we lose the QMOE link. We tested the failover/failback and it works great.

As part of this initial setup, we redirected Internet traffic for our Gilbert users across the new link and out the Mesa campus ISP connection. Currently, the Gilbert campus has a T1 connection to our ISP. The Mesa campus has a Cox Business Internet line, 12Mb down and 2Mb up. We will see how this works for us. Failover for this setup, at this time, is a manual process. We are working on automating that as well.

We did not set up any QoS today. But, our plan is to configure QoS to improve transmission for video and voice traffic, mainly video. Our hope is to, at some point in the not-too-distant future, stream services between the campuses in real time. But, that is a future blog post.

I am excited to see the performance of this link moving forward. I am especially interested in getting responses from our Gilbert campus users who regularly use Mesa campus resources.

Wednesday, October 8, 2008

New Backup Solution Going In...

Scalar 50So, after pulling together a bunch of different pieces, we are just about ready to deploy our DPM solution. Pieces have included SAN storage, the tape library (pictures at left), re-tasking a server, etc. Things ended up being much more complicated and time-consuming than I originally thought. But, we are now very close.

The tape library was a huge piece for this, since our backups have been running over one tape for some time now. Our Scalar 50 has one tape drive and supports up to 30 tapes. Our DPM installation will perform D2D2T backups for a variety of our servers and applications. We will be doing straight D2T for other platforms until we get some more SAN disk.

This implementation should greatly enhance our DR and backup solution over our current install... BackupExec with a single tape drive. My plan is to deploy this gear tomorrow and work on configuring Protection Groups over the weekend. Fun stuff! Hopefully, I will be able to sleep better at nights knowing DPM is protecting my data.

Thursday, October 2, 2008

Major Update to my New-SharedFolder script...

More PowerShell geekery ahead!

I've written about this before, here and here. But, I use it so often that I am constantly thinking about how to improve it. Well, today, I made what I feel is a major leap regarding the functionality of this tool.

The main goal of my efforts today was to make this tool more Powershell-y... that is, not so much a script to be run, but a function that now interacts with the pipeline and accepts parameters.

My previous version of this script was normally run dot-sourced and the session looked something like:

image

It required: 1)being run, 2)selecting <1 or 2>; 3)typing of foldername or .txt file path

It was basically a mini-app. This was OK, but not really in the spirit of Powershell. So, I re-wrote things. Actually, I re-organized things more than re-wrote them. Anyway, now the process looks like:

image

It is now a function. So, I can now perform this operation the following ways:

  • 'folderName1','folderName2' | New-SharedFolder
  • 'c:\scripts\folderList.txt' | New-SharedFolder
  • 'folderName', 'c:\scripts\folderList.txt' | New-SharedFolder
  • New-SharedFolder 'folderName'
  • etc...

The function accepts input of a folder name and/or a filename (with path). It runs a Test-Path on the input and, if it finds the file, it reads its contents. If the Test-Path fails, it assumes that a folder name was entered.

Here's the new code, in case you are interested. I am sure there are many things in this code that some PoSH gurus out there might roll their eyes at. But, for me, it is a big leap forward. I know that I am not done working on this function.

function New-SharedFolder
{
    param ( [string]$inputString )

    BEGIN
    {
        # Usage Instructions
        function Usage() 
        {
            <Code to display docs>
        }
        
        # Loads the foldernames in array for processing
        function load_folderlist_array
        {
            param ( [string]$inStr )
            
            if (Test-Path $inStr)
            {
                $fl = Get-Content $inStr
            }
            else
            {
                $fl = $inStr
            }
            return $fl
        }
        
        # Displays help
        if (($Args[0] -eq "-?") -or ($Args[0] -eq "-help")) 
        {
            Usage
            break
        }

        # This Function creates folder and groups
        function process_folders 
        {
            param([array]$allFolderNames)
                        
            foreach ($folder in $allFolderNames) 
            {
                Write-Host "************************************************" -foregroundcolor White
                Write-Host "Now processing folder name: $folder"
        
                # Defining path to folder
                $FullPath = "\\FS01\Groups\" + $folder
            
                #Does this folder already exist?
                if (Test-Path $FullPath) 
                {
                    #Folder already exists. Abort operation.
                    Write-Host -ForegroundColor Red "The folder $FullPath already exists and is being skipped. Please verify the folder name."
                }
                else 
                {
                    # Create variables holding group names and descriptions
                    # If necessary, shorten folder name to 13 characters
                    if ($folder.length -gt 13) 
                    {
                        $MODgroup = "s_$($folder.substring(0,13))_MOD"
                        $MODdesc = "$folder -- MODify security group"
                        $ROgroup  = "s_$($folder.substring(0,13))_RO"
                        $ROdesc = "$folder -- ReadOnly security group"
                    }
                    else 
                    {
                        $MODgroup = "s_$($folder)_MOD"
                        $MODdesc = "$folder -- MODify security group"
                        $ROgroup  = "s_$($folder)_RO"
                        $ROdesc = "$folder -- ReadOnly security group"
                    }
                    # Defines OU location for Security Group Creation
                    $GroupContainer = <Path to OU>
                    # Create Groups
                    Write-Host "Creating MODify security group for this folder..." -NoNewline
                    $null = New-QADGroup -ParentContainer $GroupContainer -name $MODgroup -samAccountName $MODgroup -GroupType 'security' -GroupScope 'GLOBAL' -description $MODdesc
                    Write-Host "  DONE!" -foregroundcolor Green
                    Write-Host "Creating ReadOnly security group for this folder..." -NoNewline
                    $null = New-QADGroup -ParentContainer $GroupContainer -name $ROgroup -samAccountName $ROgroup -GroupType 'security' -GroupScope 'GLOBAL' -description $ROdesc
                    Write-Host "  DONE!" -foregroundcolor Green
        
                    # Create Folder
                    Write-Host "Creating folder under \\FS01\Groups ..." -NoNewline
                    $null = New-Item -path \\FS01\Groups -name $folder -type directory
                    Write-Host "  DONE!" -foregroundcolor Green
                
                    # Check for security groups on all DCs
                    wait_for_replication
                    
                    # Assign rights to the folder for the groups
                    #   MODIFY RIGHTS
                    Write-Host "`nAssigning MODify ACL entries for this folder to the MOD group..."
                    assign_rights "Modify" $MODgroup
                                
                    #   READONLY RIGHTS
                    Write-Host "Assigning ReadOnly ACL entries for this folder to the RO group..."
                    assign_rights "Read" $ROgroup
                }
            }
        }

        # Checks all DCs for security groups, verifying replication
        function wait_for_replication 
        {
            $DCs = Get-QADComputer -ComputerRole DomainController
            
            foreach ($DC in $DCs)
            {
                $aa = $null
                $bb = $null
                Write-Host "`nChecking for Security Group on $($DC.Name)" -noNewLine
                
                do
                {
                    $null = Connect-QADService -Service $DC.Name
                    $aa = Get-QADGroup $MODgroup
                    $bb = Get-QADGroup $ROgroup
                    Disconnect-QADService 
                    Start-Sleep -Seconds 1
                    Write-Host '.' -noNewLine
                }
                until ($aa -ne $null -and $bb -ne $null)
            }
        }
    
        # Modifies ACL on folder, giving MOD and RO groups appropiate rights
        function assign_rights 
        {
            param([string]$Rights, [string]$GroupName)
            
            $acl = get-acl $FullPath
            $Inherit = [Security.AccessControl.InheritanceFlags] "ContainerInherit, ObjectInherit"
            $Prop = [Security.AccessControl.PropagationFlags] "None"
            $NewRule = new-object Security.AccessControl.FileSystemAccessRule $GroupName, $Rights, $Inherit, $Prop, Allow
            $modified = $FALSE
            $modded = $acl.ModifyAccessRule("Add", $NewRule, [ref]$modified)
            set-acl -path $FullPath -AclObject $acl
            
            if ($modded) 
            {
                Write-Host "ACL for $GroupName successfully applied." -foregroundcolor Green
            }
            else 
            {
                Write-Host "WARNING!!! ACL for $GroupName failed to apply!" -ForegroundColor Red 
            }
        }    
    }

    PROCESS
    {
        if ($inputString -and $_)
        {
            Throw 'Please use either pipeline or input parameter'
            break
        }
        elseif ($inputString)
        {
            $folderNames = load_folderlist_array $inputString
        }
        elseif ($_)
        {
            $folderNames = load_folderlist_array $_
        }
        else
        {
            Usage
            break
        }
        
        process_folders $folderNames
    }
    
    END
    {
        Write-Host "`nJob Complete." -foregroundcolor White
    }
}

Additional Info

My photo
email: support (AT) mangrumtech (DOT) com
mobile: 480-270-4332