Hey, Scripting Guy! Question

Hey, Scripting Guy! I am trying to create a bunch of users via script, but I am having problems dealing with their passwords. I do not want to give everyone the same password, and I do not want to have to manually create a hundred passwords and store them in a file, so I am nearly at a loss. I have seen some password generator programs on the Internet, and I think I need something like that, but I would love to be able to do it in a script instead. The one I downloaded had a user interface and did not appear to be scriptable. Can you create passwords in a script using Windows PowerShell?

- RS

SpacerHey, Scripting Guy! Answer

Hi RS,

You know, one time my dad gave me a beautiful hammer. The hammer had a forged 12-ounce head and a carved rosewood handle with a leather grip. It was the nicest tool I had ever seen. It was way too good to leave laying around in my wood working shop, so I brought it into the house. The problem is that my hammering needs in the house were always a bit limited. So, if I had a screw that needed installing, I reached for the hammer. If I had ice that needed crushing, hammer time. Need to fix the TV set, hammer. The day I decided to see if my magic hammer would be any good at breaking eggs was the day the scripting wife told me to take my hammer back to the shop.

What's the point of all this? Well, RS, sometimes tools are designed for a specific purpose, and while they may be great at that particular task, they are not so hot at other jobs for which they were not designed and were never intended. So while your Internet password generator may be good at creating a list of random passwords, it was probably never intended for that purpose. We could possibly use the SendKeys method from the WshShell object to operate your program. Here is an example of using SendKeys (from VBScript fame):

$wshShell = New-Object -ComObject wscript.shell
[void]$wshShell.Run("calc")
[void]$wshshell.AppActivate("calculator")
Start-Sleep -milliseconds 100
$wshShell.SendKeys("1{+}2~")
Start-Sleep -milliseconds 100
$wshShell.SendKeys("*5~")

You can see that using SendKeys in Windows PowerShell works exactly the same as the way it worked in VBScript. The first thing we do is create an instance of the WshShell object. We do this by using the New-Object cmdlet. In VBScript, we would use CreateObject instead of New-Object. We need to tell New-Object that we are creating a COM object and give it the program ID. This is seen here:

$wshshell = New-Object -ComObject wscript.shell

We then use the Run method from the WshShell object contained in the $wshShell variable to launch an instance of the Calculator program. We could have just typed Calc instead of using the Run method, but hey, we already had the WshShell object, so we may as well use it. The next thing we do is bring Calculator to the foreground so that we can work with it. There are two tricky things here. The first is that the name of the executable is Calc.exe, so we need to use Calc when using the Run method. The window title is Calculator, so we need to use that with the AppActivate method. But you probably already knew that because it is the same as the way it works in VBScript. These two lines of code are seen here:

[void]$wshShell.run("calc")
[void]$wshshell.AppActivate("calculator")

Now we need to pause for a little bit to make sure the application is completely loaded and in the foreground prior to sending key strokes to it. So we use the Start-Sleep cmdlet to do this. In VBScript we would use wscript.sleep. It is essentially the same thing. Once we are certain the application is in the foreground, we use the SendKeys method to send key strokes to the application. In this example we add 1+2 and press ENTER. We wait a bit, and then send more key strokes to multiply the previous result by the number 5, and the we press ENTER again. This is seen here:

Start-Sleep -milliseconds 100
$wshShell.SendKeys("1{+}2~")
Start-Sleep -milliseconds 100
$wshShell.SendKeys("*5~")

What does all this have to do with your question, RS? It might be possible to use SendKeys to control your external application. It is just something to think about. However, I have a better idea in this particular case. How about if we write our own password generator? If you think this will be cool, read on. If not, you are free to take the SendKeys example and use that to try to automate your password generator program. It won’t hurt my feelings. I had fun writing it.

Param($pwdPass = 2)
Function CreatePassword()
{
 $rnd = new-object system.random
 for($i = 1 ; $i -le $pwdPass ; $i++)
 {
   LtrFmAscii($rnd.Next(33,47))
   LtrFmAscii($rnd.Next(48,57))
   LtrFmAscii($rnd.Next(65,90))
   LtrFmAscii($rnd.Next(97,122))
 }
} #end CreatePassword
Function LtrFmAscii($ascii)
{
  $script:password += [char]$ascii
} #End LtrFmAscii
Function DisplayPassword()
{
  $script:password
} #end DisplayPassword
# *** ENTRY TO SCRIPT ***
$script:password = $null
CreatePassword
DisplayPassword

The first thing we do in this script is define a command-line parameter named pwdPass. This parameter is used to control how many times we work through the CreatePassword function. Each pass through the CreatePassword function adds one special character, one number, one lowercase letter, and one uppercase letter. This line of code is seen here where by default we set the value of the $pwdPass variable equal to 2:

Param($pwdPass = 2)

You may be thinking that we just said the command-line parameter is pwdPass, but we said the variable $pwdPass contains the value 2. How is this possible? Well, when the script is run from the command line, we use -pwdPass as seen here:

-pwdPass used from the command line

 

You can also see that the command line parameter is not case sensitive.

Next, we create the CreatePassword function where we first create an instance of the System.Random .NET Framework class and store the object in the $rnd variable. Next, we use the for statement to create a loop that obtains random numbers from four different sections of the ASCII character table. Each random number from the specified numeric range is sent to the LtrFmAscii function where we will turn the integer into a character. The CreatePassword function is seen here:

Function CreatePassword()
{
 $rnd = new-object system.random
 for($i = 1 ; $i -le $pwdPass ; $i++)
 {
   LtrFmAscii($rnd.Next(33,47))
   LtrFmAscii($rnd.Next(48,57))
   LtrFmAscii($rnd.Next(65,90))
   LtrFmAscii($rnd.Next(97,122))
 }
} #end CreatePassword

The next thing we need to do is create the helper function LtrFmAscii. This function is used to change the integer obtained from the CreatePassword function into a character (an instance of a system.charvaluetype class). We use the += operator to concatenate the characters into a single string we can use for our password. We use the script-level variable $password to hold the string that is being built. This is seen here:

Function LtrFmAscii($ascii)
{
  $script:password += [char]$ascii
} #End LtrFmAscii

When we have the generated password, we go to the DisplayPassword function. The DisplayPassword function is perhaps not too sophisticated—all it does is print the password to the screen. However, in future versions of this script, we may want to write the password to a text file, or send the password by e-mail to the newly created user's manager. There is a lot of potential here so we decided to put the logic into a separate function that is seen here:

Function DisplayPassword()
{
  $script:password
} #end DisplayPassword

That's it for the functions. The entry point to the script declares the $password variable as a script-level variable, which means it can be used anywhere within the script. To do this, we use the script modifier as seen here:

$script:password

We go ahead and initialize the $password variable by setting it equal to $null. The next thing we do is call the CreatePassword function and the DisplayPassword function. The entry to the script is shown here:

# *** ENTRY TO SCRIPT ***

$script:password = $null
CreatePassword
DisplayPassword

So, RS, that is about all there is to generating random passwords. In our next article, we will show you how to incorporate it with creating users in Active Directory. As they say in Australia (deep-southern Australia), "Y'all come back now, ya hear?"

Ed Wilson and Craig Liebendorfer, Scripting Guys