Bookmark and Share

 

(Note: These solutions were written for Advanced Event 3 of the 2010 Scripting Games.)

 

Advanced Event 3 (Windows PowerShell)


Photo of Shay Levy


Shay Levy
Windows PowerShell MVP
Blog: http://blogs.microsoft.co.il/blogs/ScriptFanatic
Twitter: http://twitter.com/ShayLevy
Download the PowerShell Community Toolbar: http://tinyurl.com/PSToolbar

 

For this event we are going to write an advanced function. With advanced functions, we can create robust parameters. We can set parameters with default values, validate parameters input, choose their position, decide if we want a parameter to accept pipeline input, and more.


The function defines three parameters:  

            FolderPathHolds the path to the class folder; default value is c:\fso.

            ClassNameHolds a collection of class names to create. Default values are:


·        
Shakespeare

·         World Literature            

·         Calculus 2

·         Physics

·         Music Appreciation

·         American Literature Since 1850


The ClassName parameter accepts pipeline input by value. That means we can pipe class names (see example number 4).

           
PickFolderGUIIf specified the caller can select or create the class folder in a GUI dialog.



The function uses two script blocks: Begin and Process.


In the Begin clause, we initialize global values that we want to use in the function, such as the class folder name and week number of the year. We also test if the class folder exists. If it doesn't exist, we will create it. If the caller specifies that he wants to use a GUI dialog to choose a folder, he can choose or create that folder using that dialog.

 

The GUI dialog is implemented using the Shell.Application COM object. The reason to choose COM over the .NET Framework System.Windows.Forms.FolderBrowserDialog class is that the .NET Framework class requires Windows PowerShell to be running in STA mode and default instances of Windows PowerShell are running by default in MTA mode.


In the function process block, we iterate all class names and create the class files in the designated directory. To allow files to be saved along with the week number, we can use the Get-Date cmdlet. One of the members of datetime is the DayOfYear. To get the week number we divide its value by 7 and send the result to System.Int so that we can get a number without any decimal point.


All class files for a given week are saved in a subdirectory by the name of Week xx.


Function Set-ClassFolder

{

 

      [CmdletBinding()]

 

      Param(

            [Parameter(Position=0,HelpMessage="The path to the class folder.")]

            [System.String]$FolderPath="C:\fso",

           

            [Parameter(Position=1,ValueFromPipeline=$true,HelpMessage="An array of class names.")]        

            [System.String[]]$ClassName=@('Shakespeare','World Literature','Calculus 2','Physics','Music Appreciation','American Literature Since 1850'),

           

            [switch]$PickFolderGUI       

      )

 

      begin

      {

            $weekNumber = "Week "+([int]((Get-Date).DayOfYear/7)).ToString("D2")

            $classFolder = Join-Path $FolderPath $weekNumber

 

            if($PickFolderGUI)

            {

                  $shell = New-Object -ComObject Shell.Application

                  $folder = $shell.BrowseForFolder(0,"Select the class folder",0,"")

 

                  if($folder)

                  {

                        $classFolder = Join-Path $folder.Self.Path $weekNumber

                  }

                  else

                  {

                        Write-warning "You pressed the Cancel button. The script will use '$FolderPath' as the default folder."

 

                        if(!(Test-Path $FolderPath -PathType container))

                        {

                              Write-Warning "The default class folder doesn't exist; creating folder '$FolderPath'"

                              $null = New-Item -Path $classFolder -Type directory -Force

                        }

                  }

            }

            else

            {

                  if(!(Test-Path $FolderPath -PathType container))

                  {

                        Write-Warning "'$FolderPath' doesn't exist; creating folder"

                        $null = New-Item -Path $FolderPath -Type directory -Force

                  }                

            }

      }

 

      process

      {

            foreach($class in $ClassName)

            {

                  $FileName = "$(Get-Date -f yyyy_MM_dd_)$class.txt"

                  $notes = "CLASS: $class`r`nDATE: $((Get-Date).ToShortDateString())`r`nNOTES: `r`n"

                  $null = New-Item -Path $classFolder -Name $FileName -Type File -Value $notes -Force                      

            }          

      }

}


Sample usage:


1.    
Run the function without any parameters, using the default values. Class files will be created in the default folder (C:\Fso) under a subfolder with the current week number:


PS > Set-ClassFolder


2.    
Run the function without any parameters using the default values and use a GUI dialog to select or create another folder path to store the class files:


PS >  Set-ClassFolder -PickFolderGUI


3.    
Override the default class names and write the files to another folder:


PS > $class = 'Math','English Litrature','Biblical Hebrew'
PS > Set-ClassFolder -FolderPath c:\fso1 -ClassName $class


4.    
Same as #3 with pipeline support and use a GUI to select/create the class files folder:


PS > $class | Set-ClassFolder –PickFolderGUI

 


The second function for this solution is Get-ClassFile. We can use it to open a specific class file in the default text file editor.


Examples:


1.    
Open class name Physics in your default text editor; if the file name doesn't exist, you get an error:


PS > Get-ClassFile -FolderPath C:\fso -WeekNumber 14 -FileName Physics


2.    
Open class name Physics in your default text editor; when you specify the -Force parameter, the file is created if it doesn't exist.


PS > Get-ClassFile -FolderPath C:\fso -WeekNumber 14 -FileName Physics  -Force

 

Function Get-ClassFile

{

 

      [CmdletBinding()]

 

      Param(

            [Parameter(Position=0,HelpMessage="The path to the class folder.")]

            [System.String]$FolderPath="C:\fso",

           

            [Parameter(Position=1,Mandatory=$true,ValueFromPipeline=$true,HelpMessage="The class file name without extension.")]          

            [System.String]$FileName,

 

            [Parameter(Position=2)]      

            [System.String]$WeekNumber=([int]((Get-Date).DayOfYear/7)).ToString("D2"),

           

            [switch]$Force

      )

 

      $file = (Join-Path $FolderPath "Week $WeekNumber") +"\*$FileName.txt"

 

      if(!(Test-Path -Path $file -PathType leaf))    

      {

            if($Force)

            {

                  $Name = "$(Get-Date -f yyyy_MM_dd_)$FileName.txt"

                  $notes = "CLASS: $FileName`r`nDATE: $((Get-Date).ToShortDateString())`r`nNOTES:`r`n"

                  $null = New-Item -Path "$FolderPath\Week $WeekNumber" -Name $Name -Type File -Value $notes -Force

                  if($?)

                  {

                        Invoke-item $file

                  }

            }

            else

            {

                  Write-Error "Class file: $file doesn't exist"

            }

      }

      else

      {

            Invoke-item $file

      }

}

 


Advanced Event 3 (VBScript)


Microsoft Scripting Guy Ed Wilson answered Advanced Event 3 (VBScript) as well. His solution is shown here.


At first blush, Beginner Event 3 and the Advanced Event 3 are similar. The design criteria, however, require a different approach.


Because I decided to present a dialog box to allow for selecting the destination or for creating a new folder, you need to check to ensure the script is running under CScript. A subroutine, shown here, takes care of that task:


Sub subCheckCscript
If UCase(Right(Wscript.FullName, 11)) = "WSCRIPT.EXE" Then
    Wscript.Echo "This script must be run under CScript"
    WScript.Quit
End If
end Sub


Next the shell.application object is created, and the BrowseForFolder method called. The selected folder from the dialog box shown in the following image is stored in the strPath variable:

Image of selected folder stored in strPath variable



The code that creates the folder browser is shown here:


Set objShell = CreateObject("Shell.Application")
Set objFolder = objShell.BrowseForFolder(WindowHandle, "Select a folder:", folderOnly)
Set objFolderItem = objFolder.self
strPath = objFolderItem.path


The class names are stored in an array. Use the Array function to create the array in VBScript, as shown here:


aryClassNames = Array("Shakespeare","World Literature","Calculus 2","Physics",_
"Music Appreciation","American Literature since 1850")


The file name will include the date. Use the Date function to return the current date, and then replace the forward slash in the date with an underscore:


strDate = Replace(Date,"/","_")


The file path uses the class name from the array, and the date that was created using the underscore. The class name is filled out in the text file, as is the date:


strFilePath = strPath & "\" & strClassName & "_" & strDate & ".txt"
strText = "CLASS: " & strClassName & VbCrLf & "DATE: " & Date & VbCrLf & "NOTES:"



AdvancedEvent3.vbs

Option Explicit
Dim strPath
Dim objFSO
Dim aryClassNames
Dim strClassName
Dim strFilePath
Dim strDate
Dim strText
Dim objFile
Dim objShell
Dim objFolder
Dim objFolderItem
Const windowHandle = 0
Const FolderOnly = 0

subCheckCscript   'Check to see if running in CScript

Set objShell = CreateObject("Shell.Application")
Set objFolder = objShell.BrowseForFolder(WindowHandle, "Select a folder:", folderOnly)
Set objFolderItem = objFolder.self
strPath = objFolderItem.path
aryClassNames = Array("Shakespeare","World Literature","Calculus 2","Physics",_
"Music Appreciation","American Literature Since 1850")
strDate = Replace(Date,"/","_")

Set objFSO = CreateObject("Scripting.FileSystemObject")
For Each strClassName In aryClassNames
 strFilePath = strPath & "\" & strClassName & "_" & strDate & ".txt"
 strText = "CLASS: " & strClassName & VbCrLf & "DATE: " & Date & VbCrLf & "NOTES:"
 WScript.Echo "Creating " & strFilePath
 Set objFile = objFSO.CreateTextFile(strFilePath)
 objFile.WriteLine(strText)
 objFile.Close
Next

Sub subCheckCscript
If UCase(Right(Wscript.FullName, 11)) = "WSCRIPT.EXE" Then
    Wscript.Echo "This script must be run under CScript"
    WScript.Quit
End If
end Sub


When you run the script under CScript, the output shown in the following image appears. As you can see, to run a VBScript under CScript, you open a command prompt, type cscript and the path to the script you wish to run.

Image of output seen when script is run under CScript 

 

If you want to know exactly what we will be looking at tomorrow, follow us on Twitter or Facebook. If you have any questions, send e-mail to us at scripter@microsoft.com or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

 

Ed Wilson and Craig Liebendorfer, Scripting Guys