Learn PowerShell by Rewriting VBScript

If you’re a busy admin and you are new to PowerShell, you might wonder just where to start.  Yes, you’ve downloaded PowerShell and perhaps played with it, trying a few lines of script.

But how can you learn all that from scratch?

If you know VBScript, why not convert a few short scripts to PowerShell?  Here’s one example, stolen from SAPIEN Technology’s blog:  Play WAVs with SAPI.

This script uses the Microsoft Speech API in an inventive way to play WAV files in the Windows\Media directory.  It first speaks the name of the file, then plays it. 

Here’s my version:

 

# speak-wav.ps1
function DemoWav {
param ($File)
trap {return}
    $objFile.Open($file.FullName)
    $objSPVoice.Speak($file.Name)
    $objSPVoice.SpeakStream($objFile)
    $objFile.Close()
return;
}

$objSPVoice = new-object -com "SAPI.SpVoice"
$objFile = new-object -com "SAPI.SpFileStream"

$strMediaDir = join-path $Env:Windir -childpath "Media"
get-childitem $strMediaDir -filter "*.wav" | foreach {
    write-output $_.name
    demowav $_
    }

Let’s deconstruct this:  PowerShell, unlike VBScript, requires you to declare your functions up-front before they’re called by the main code.  Function Demowav flows and performs the same way as in the VBScript version, so we’ll get back to that later.

Continuing on to the start of the main code,  almost all VB scripts except the very simplest access COM objects like so:

Set objSPVoice = CreateObject("SAPI.SPVoice")

In PowerShell this is:

 

$objSPVoice = new-object -com "SAPI.SpVoice"

This is one of the most common conversions you’ll see.  Remember that references to objects within VBScript are almost always COM or ActiveX so you need to specify the -com option in new-object or else PoweShell will think it’s a .Net object and error out.

Another place where the idioms of VBScript and PowerShell are very different is in file and directory processing.  VBScript uses a file system object at a very low level, so your script code has to do all the low-level work of traversing directories and resolving file paths, as in this code:

 

strWinDir= objShell.ExpandEnvironmentStrings("%WinDir%")
strMediaDir=strWinDir & "\Media"

Set objFSO=CreateObject("Scripting.FileSystemObject")
Set objFiles=objFSO.GetFolder(strMediaDir).Files

For Each file In objFiles
'only get .wav files
    If Right(file.name,3)="wav" Then 
    WScript.Echo file
    DemoWav file
    End If
Next

I am sad to recall that in the early days of Windows NT scripting, I was having to do stuff like this in Perl. Parsing a string to get the extension?  Ewww?  Going to yet another object to get the environmental variables? 

To get the %Windir% environment variable we simply do this in PS:

$Env:Windir

Here’s the main body of our code:

$strMediaDir = join-path $Env:Windir -childpath "Media"
get-childitem $strMediaDir -filter "*.wav" | foreach {
    write-output $_.name
    demowav $_
    }

Join-path simply takes two filenames and joins them in a path, so that $Env:Windir and "Media" become (when resolved) "C:\windows\media\".

get-childitem is the most important command:  This cmdlet, when applied to a filesystem, will return a list of objects corresponding to files in that directory.  The -filter parameter tells the cmdlet to filter the output as specified, so in this case, we are only returning *.WAV files.

Learn this cmdlet;  you’ll use it everywhere. 

The pipe "|" character is where the fun starts.  I mentioned that get-childitem returns a list of objects.  These objects get piped to the next statement:

| foreach {
    write-output $_.name
    demowav $_
    }

That’s our loop. It’s very similar to the VBScript.  Note the $_ variable.  You’ll see this everywhere.  That variable just means "current object in the pipeline".  So for each WAV file, the script writes the name of the file to the console with write-host and calls our function demowav.  Note that there are no parentheses in function calls in PowerShell, unlike VBScript and everywhere else.

Now we’re back to the function at the top of the file:

 

function DemoWav {
param ($File)
trap {return}
    $objFile.Open($file.FullName)
    $objSPVoice.Speak($file.Name)
    $objSPVoice.SpeakStream($objFile)
    $objFile.Close()
return;

This is straightfoward and almost identical to the VBScript code.  param just declares valid function and program parameters (remember, no parens for function declarations in PS!)  trap {return} just tells this function to return from the function in case of errors.  The only point of difference is that $file is an object, rather than just a string representing a file name, so we need to specify $file.FullName–which is the file’s complete path–when we open the file.  ($File.Name provides just the name without the full path.)

This was a long post, but if you got through it, and have basic knowledge of PowerShell, you’ll be able to convert short scripts nearly in your head.  It took me two hours to write this post but only 10 (!) minutes to co

nvert the original script. 

Microsoft’s Script Center has a guide on converting VBScript to PowerShell that you’ll find useful.

Have Fun!

Dave

del.icio.us tags:

Advertisements


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s