Learn about Windows PowerShell
(Note: These solutions were written for Advanced Event 3 of the 2010 Scripting Games.)
Shay LevyWindows PowerShell MVPBlog: http://blogs.microsoft.co.il/blogs/ScriptFanaticTwitter: http://twitter.com/ShayLevyDownload 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:
FolderPath—Holds the path to the class folder; default value is c:\fso.
ClassName—Holds 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).
PickFolderGUI—If 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
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
[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
Write-Error "Class file: $file doesn't exist"
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 subCheckCscriptIf UCase(Right(Wscript.FullName, 11)) = "WSCRIPT.EXE" Then Wscript.Echo "This script must be run under CScript" WScript.QuitEnd Ifend 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:
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.selfstrPath = 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 ExplicitDim strPath Dim objFSODim aryClassNamesDim strClassNameDim strFilePathDim strDateDim strTextDim objFileDim objShellDim objFolderDim objFolderItemConst windowHandle = 0Const FolderOnly = 0subCheckCscript 'Check to see if running in CScriptSet objShell = CreateObject("Shell.Application")Set objFolder = objShell.BrowseForFolder(WindowHandle, "Select a folder:", folderOnly)Set objFolderItem = objFolder.selfstrPath = objFolderItem.patharyClassNames = 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 NextSub subCheckCscriptIf UCase(Right(Wscript.FullName, 11)) = "WSCRIPT.EXE" Then Wscript.Echo "This script must be run under CScript" WScript.QuitEnd Ifend 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.
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