Hey, Scripting Guy! Event 5 *Solutions* from Expert Commentators (Beginner and Advanced; the 400-meter race)

  

(Note: These solutions were written for Event 5.)  


Beginner Event 5: The 400-meter race

The 400-meter race is a common track event. You will therefore be asked to perform the very common task of reading from the registry and writing to the registry.  

Guest commentator: Mark Schill

Image of guest commentator Mark Schill

Mark Schill is a Systems Engineer for an international services company that specializes in systems automation. He is an advisory committee member for PowerShellCommunity.org. and is also a moderator for the Official Scripting Guys Forum. Mark also maintains the String Theory blog.


VBScript solution

The first thing I decided to do was to create a function that takes the number of downloads as an input parameter. I prefer to use functions in my scripts whenever possible because it allows me to reuse my code easily in other scripts. The next step was to actually determine the correct registry key to modify. Because I had no idea which key it was, I had to look it up on the Internet.

I then used the RegWrite method of the WScript.Shell object to write to the registry key. To use the RegWrite method, you must first create an instance of the WshShell object. This is seen here.

Set objShell = WScript.CreateObject(“WScript.Shell”)

The RegWrite method takes three parameters. The first is the registry key, the second is the value, and the last parameter is the type of registry key it is. It is seen here:

DownloadKey = “HKCUSoftwareMicrosoftWindowsCurrentVersionInternet SettingsMaxConnectionsPer1_0Server”
objShell.RegWrite DownloadKey, intNumberofDownloads, “REG_DWORD”

I could also have used Windows Management Instrumentation (WMI) to perform the writing, but I thought this was simpler. And because we have to work with HKEY_Current_User, we cannot edit the remote registry. I also did not have to worry about whether the key exists because the RegWrite method will create the value if it does not exist.

BeginnerEvent5Solution.vbs

SetDownloadJobsNumber 10
Function SetDownloadJobsNumber( intNumberofDownloads)
  Set objShell = WScript.CreateObject(“WScript.Shell”)
  objShell.RegWrite “HKCUSoftwareMicrosoftWindowsCurrentVersionInternet SettingsMaxConnectionsPer1_0Server”, intNumberofDownloads, “REG_DWORD”
  Downloads = objShell.RegRead(“HKCUSoftwareMicrosoftWindowsCurrentVersionInternet SettingsMaxConnectionsPer1_0Server”)
          WScript.Echo “Current Downloads: ” & Downloads
End Function

To provide verification to the user, I used the RegRead method from the WshShell object to read the value to a variable in order to display it to the screen:

Image of verification displayed


Guest commentator: Tobias Weltner 

Image of guest commentator Tobias Weltner

Dr. Tobias Weltner is Germany’s first Microsoft MVP for Windows PowerShell. He has written two Windows PowerShell books for Microsoft Germany, and trains and coaches throughout Europe. He loves to share his knowledge on his International Web site and is also a Windows PowerShell.com community director. As a developer, he created a number of scripting editors and development environments to make scripting easier, such as Systemscripter (VBScript) and the new PowerShellPlus, which is a Windows PowerShell rapid development interactive console, editor, and debugger. PowerShellPlus was selected as a finalist for “Best of Show” at TechEd 2009 in Los Angeles. You can contact Tobias at tobias@powershell.com.


Windows PowerShell solution

By default, Internet Explorer allows only two simultaneous downloads. Given today’s bandwidth, this limitation is a bit outdated, so let us use Windows PowerShell to lift it.

First, you must know where exactly the setting is stored inside the registry. As it turns out, the Internet Explorer download limitation is stored in two DWORD values called MaxConnectionsPer1_0Server and MaxConnectionsPerServer. These values are located here: HKEY_CURRENT_USERSoftwareMicrosoftWindowsCurrentVersionInternet Settings.

Next, we must to read and write registry values. Windows PowerShell makes it really easy to create keys because it supports registry “drives” like HKCU: and HKLM:, and you can use file system commands to create and delete keys. Reading and writing registry values inside a key is not that easy though. This is why I decided to first create two generic new functions called Get-RegistryValue and Set-RegistryValue. They can read and write any registry value. Let us check out Get-RegistryValue function first.

Get-RegistryValue uses Get-ItemProperty to retrieve the value. There are two things to note. First, I do not use any virtual registry drive but instead specify the registry provider (“Registry::”). This way, I can read just any registry key and do not have to know specific virtual drive names. Second, Get-ItemProperty returns not just the value but also a bunch of additional properties, wrapped as an object. Because I just want to know the value, I enclose Get-ItemProperty in parentheses and access only the value that I find in a property with the same name as the valuename. Isn’t it cool how you can access object properties through variables?

The Set-RegistryValue function accepts four parameters: the registry key you want to write the value to, the value name, its value, and optionally the data type. The fourth parameter defaults to “String”, but when we later set the Internet Explorer settings, we need a numeric DWORD value and can simply specify “DWORD” to get just that kind of entry. Allowable types are String, ExpandString, Binary, Dword, MultiString and QWord.

Now that I can read and write to the registry, I create the functions Get-MaxIEDownloads and Set-MaxIEDownloads. Internally, they use the previous generic functions to read and write the Internet Explorer settings with the correct location. Because the Internet Explorer limit is stored in two different locations, I use the .NET math function Min() to return the lower of both in case they are different. Note also that I added a trap statement so in case something unexpected happens, the script exits gracefully with a meaningful message. Note also the use of the exit statement. With it, you exit the function without an additional error message.

At this time, you could already set a new Internet Explorer limit by using these functions:

Set-MaxIEDownloads 30

Get-MaxIEDownloads

Finally, I added yet another function called Enter-MaxIEDownloads, which prompts for a new value. (Optionally, you can uncomment additional lines to get a nice InputBox(),  dialog which I “borrowed” from the VB.NET assemblies.) Enter-MaxIEDownloads also uses a switch statement to validate the user input in case a user enters anything invalid. The complete BeginnerEvent5Solution.ps1 script is seen here:

BeginnerEvent5Solution.ps1


function Get-RegistryValue($key, $valuename) {
          (Get-ItemProperty “Registry::$key”).$valuename
}

function Set-RegistryValue($key, $valuename, $value, $type = ‘String’) {
          Set-ItemProperty “Registry::$key” $valuename $value -type $type
}

function Get-MaxIEDownloads {
          trap {
                   “Unable to read registry value: $($_.Exception.Message)”
                   exit 1
          }

          $key = ‘HKEY_CURRENT_USERSoftwareMicrosoftWindowsCurrentVersionInternet Settings’
          $valuename1 = ‘MaxConnectionsPer1_0Server’
          $valuename2 = ‘ MaxConnectionsPerServer’

          # read current max number of downloads
          $value1 = Get-RegistryValue $key $valuename1
          $value2 = Get-RegistryValue $key $valuename1
          # should be the same, to make sure find lower value of both
          [Math]::Min($value1, $value2)
}

function Set-MaxIEDownloads($value) {
          trap {
                   “Unable to write registry value: $($_.Exception.Message)”
                   exit 1
          }

          $key = ‘HKEY_CURRENT_USERSoftwareMicrosoftWindowsCurrentVersionInternet Settings’
          $valuename1 = ‘MaxConnectionsPer1_0Server’
          $valuename2 = ‘ MaxConnectionsPerServer’

          Set-RegistryValue $key $valuename1 $value ‘DWORD’
          Set-RegistryValue $key $valuename2 $value ‘DWORD’
}

function Enter-MaxIEDownloads {
          trap {
                   “Your input was invalid. Use a number greater than 0”
                   exit 1
                   }
         
          $oldvalue = Get-MaxIEDownloads

          [Int]$newvalue = Read-Host “Enter new number of IE downloads (current value: $oldvalue)”
         
          # ask for new number using InputBox()
          #[System.Reflection.Assembly]::LoadWithPartialName(“Microsoft.VisualBasic”) | Out-Null
          #[Int]$newvalue = [Microsoft.VisualBasic.Interaction]::InputBox(“Please enter number of maximum #simultaneous downloads (currently $oldvalue):”, “Change Downloads”, $oldvalue)

          # check input and write to registry:
          switch ($newvalue)  {
                   0         { “You cancelled” }
                   {$_ -gt 0}{
                                Set-MaxIEDownloads $newvalue
                               “New simultenous IE downloads: $newvalue”
                              }
                   default   { “Invalid input: $newvalue” }
          }
}

Enter-MaxIEDownloads

When you run the BeginnerEvent5Solution.ps1 script, you see this confirmation message:

Image of the confirmation message displayed

 

Advanced Event 5: The 400-meter race

The 400-meter race is a common track event. For this common event, you will be asked to identify pictures that were taken with a common camera. 

Guest commentator: Gary Siepser

Image of guest commentator Gary Siepser

Gary Siepser is a senior premier field engineer for Microsoft. He is a Microsoft Certified Master in Exchange 2007 and holds several other Microsoft certifications. He spends most of his time these days mixing it up between teaching Windows Powershell and helping customers with their Exchange Servers.


VBScript solution

To write my script solution for this challenge, I first spent time researching exactly how to programmatically read the metadata. I struck out quite a bit at first. Lately, I have been using Windows Powershell exclusively, so I worked in it until I could read JPG metadata before even starting with VBScript. I found a .NET way to get the metadata out, but that will not work easily in a VBScript. Then, I tried to find a way to actually access the .NET class I was using in VBScript. That looked tough, so I just kept searching for a COM way to read metadata. I cannot believe I missed it in early searches, but right in the Windows 2000 Scripting Guide, there it was: my COM solution called Shell.Application.

I wrote a series of small test scripts to work out the details of getting just the pieces of data needed for the challenge. This was when I discovered that the chart in the above page was not right during my testing. This would haunt me later when I took the script to my Windows XP box to test it.

After I had a basic model script working (no folder scanning, just a single file metadata check), I began to focus on expanding the script to walk a folder structure. I had done this kind of thing in the past by using a recursive function. To keep the script simple, I used the scripting.filesystemobject object to loop down through any folders beneath the root folder starting point.

As I wrote the script, I wanted to break code up into functions. When I first started writing the script, I did not imagine that nearly all the code would end in functions. But that is what happened.  

So my coding experience was much like a scavenger hunt, and it was challenging at every step. After playing around with Windows Vista, odd characters started showing up in the Date Taken field. At first I could not even tell what they were. I eventually wrote a small function to go through a string character by character and then dumped out certain ones depending on their ASCII value. This really worked like a champ.

Later, I ran into another speed bump when I tested the script on Windows XP. I noticed that I was not getting back any metadata at all. This was when I went back with my earlier test scripts and realized that the index numbers were different. At least they now matched the chart on the Web page where I found the original code snippet. In the end, I had to write the operating system version, review the script, and put some behavior into the script to use different index numbers for the different operating system versions. In the AdvancedEvent5Solution.vbs script, I displayed Camera Make for Windows XP. With additional testing, we could probably get the Make on Windows XP, but I did not have the time. At the end of the day, it is not the best, most complete, or prettiest code I have ever written, but it completes the task.  

The complete AdvancedEvent5Solution.vbs script is seen here.

AdvancedEvent5Solution.vbs

‘Written by: Gary Siepser, Microsoft Premier Field Engineer

‘References:
         http://www.microsoft.com/technet/scriptcenter/guide/sas_fil_lunl.mspx?mfr=true
         http://msdn.microsoft.com/en-us/library/bb773938(VS.85).aspx
         http://www.microsoft.com/technet/scriptcenter/funzone/games/tips08/gtip1207.mspx


‘*******************************************************
‘**  User should edit the following line to be the
‘**   root location where we will look at JPGs
‘*******************************************************

strRoot = “C:TestFolder”

‘*******************************************************


‘We need to determine the operating system version early on because there are differences in the index numbers for the
‘metadata tags between the OS’…at least my Windows Vista and Windows XP boxes.  We’ll set vars for the index nums
‘differently as you see below

CurrentOSVer = OSVer
Select Case CurrentOSVer
Case “6”
          DATE_TAKEN = 12
          CAMERA_MAKE = 32
          CAMERA_MODEL = 30
Case “5”
          DATE_TAKEN = 25
          CAMERA_MODEL = 24
End Select


‘We’ll write all the output to a var rather than do a bunch of echos all over the script.  This
‘makes it much easier if you want to edit the script and say so something different than just the
‘echos.  Only one line need be edited

strMainCodeOutput = “”


‘This calls the FolderDiver function, which begins the process of walking through the folder tree

Call FolderDiver(strRoot)


‘So now thet script has returned, the line below is what makes the script echo the results. This
‘would be the line to change if you wanted to say output the results to a file or whatever

wscript.echo strMainCodeOutput



‘So here is our FolderDiver function.  This needs to be a function as we use it recursivly to walk down
‘the tree of folders.  I used the filesystem objects because it’s easy.  Later we will use folder objects
‘that come out of shell.application, but I just found it easier to use FSO here for the tree dive

Sub FolderDiver(objRoot)

          Set objFso = CreateObject(“Scripting.FilesystemObject”)  
          Set objRoot = objFSO.GetFolder(cstr(objRoot))


          ‘Here we adding to the maincodeoutput building it up.  I set up the action part of the script as a
          ‘function so I could make the calls look nice and graceful here

          strMainCodeOutput = strMainCodeOutput & ListPhotoDetailsforFilesinFolder(objroot.path)

         
          ‘Here is the recursion part, the function calls itself for any subfolder. This gives us a nice tree of function
          ‘calls and it works like a champ

          If objRoot.SubFolders.Count > 0 Then