Expert Solutions: Advanced Event 6 of the 2010 Scripting Game

Bookmark and Share

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


Advanced Event 6 (Windows PowerShell)

Andy Schneider is a systems engineer who works for Avanade in Seattle, Washington, USA. You can find his blog at


I will be the first to admit that I learned a few things while writing this script. When I saw that it had to respond to USB devices, I immediately started thinking of events and the new support for events in Windows PowerShell 2.0. I decided to work on this part of the code first. Using the WMI Query Language, I came up with this block of code:

$wql = @”

SELECT * FROM __InstanceCreationEvent


WHERE TargetInstance

ISA ‘Win32_LogicalDisk’


Let’s break it down, line by line. First, select all the events that have to do with something being created (for example, a new USB drive), and check for it every 2 seconds. Then you filter the events by making sure that what you are responding to (the TargetInstance) is a logical disk. After we have the query, we can register for those events using the Register-WMIEvent cmdlet.

Now that this is all wired up, the next step is figuring out how to do something when this event fires. There is a parameter called –action for Register-WMIEvent which is a ScriptBlock that you can specify. This ScriptBlock will run every time the event fires. There were two problems I came across when working with the –action parameter. The first was, “How do I get at the Win32_LogicalDisk info that caused the event to fire in the first place?” It turns out, within the action ScriptBlock, there are some automatic variables. I was able to find the drive device ID with the following code:

$drive = $event.SourceEventArgs.newEvent.TargetInstance.DeviceID

The second problem was a scoping issue. Within the ScriptBlock, I was not able to reference my variables in my script. Frankly, there is probably a better way to do this, but I resorted to global variables to solve this problem. This is where the $GLOBAL:_xyz variables come from.

The next issue was dealing with how Copy-Item treats files differently than directories. Copy-Item will automatically overwrite files, but it will not automatically overwrite directories unless you use the –force parameter. The logic I wrote to determine what to copy depending on what mode the script is in assumed that I either had a FileInfo object or a DirectoryInfo object. I needed these because my script used the .Name property and the .FullName property on either of these object types.

Rather than putting this logic in the body of the script, I dealt with it during parameter declaration. There is an advanced function parameter attribute called [ValidateScript]. 

param (

[Parameter( Position = 0,

           ValueFromPipeline = $true)]


[ValidateScript({($_.GetType().Name -eq “DirectoryInfo”) -or `

                 ($_.GetType().Name -eq “FileInfo”)})]



Basically, I am using the GetType() method to validate that the entries are either fileinfo or directoryinfo objects. This allowed me to specify either a group of files, folders, or any combination of the two. I also used the ValueFromPipeline so that I can pipe a dir into the script. In the Process block, I then gathered up all the pipelined objects into an array so that I can use all of them in my $action ScriptBlock.

Here is the entire script:



        Event 6 in the 2010 Scripting Games – Automatically copy files to a USB drive when it is plugged in





        The Script should allow you to select a file, a group of files, a folder, or a group of folders

        to contain the files that will be copied. The script should offer a “quiet mode” and a “prompt mode” based

        upon a switch supplied to the script when it is launched. Quiet mode should be the default. In quiet mode,

        the script copies the target files/folders to the newly inserted drive without user interaction.


        The script should run in continuous loop fashion until it is stopped, or it should offer to make a specific

        number of copies based upon a command-line argument. For example, make five copies of the data, and then stop.

        The script should offer a “force” mode that will overwrite existing files, or a “prompt” mode that will prompt

        before overwriting existing files. This should also be configurable via the command argument.



         ls *.txt | Event6.ps1



         Author: Andy Schneider





param (

[Parameter( Position = 0,

           ValueFromPipeline = $true)]


[ValidateScript({($_.GetType().Name -eq “DirectoryInfo”) -or `

                 ($_.GetType().Name -eq “FileInfo”)})]



[Parameter(Position = 2)]


$timesToCopy = 2,


[Parameter(Position = 1)]


$promptMode = $false,


[Parameter(Position = 3)]


$force = $false




Begin {


$wql = @”

SELECT * FROM __InstanceCreationEvent


WHERE TargetInstance

ISA ‘Win32_LogicalDisk’



$GLOBAL:_files = @()

$GLOBAL:_promptMode = $promptMode

$GLOBAL:_timesToCopy = $timesToCopy

$GLOBAL:_force = $force

} # End BEGIN


Process {


    $GLOBAL:_files += $file


} # End Process


End {

# Give user some feedback about which files and directories will be copied

Write-Host “The files and directories below will be copied to a USB Drive”


write-host “There are $GLOBAL:_timesToCopy copies left until automatic exit – press Ctrl+C to stop”


$action = {

    $drive = $event.SourceEventArgs.newEvent.TargetInstance.DeviceID

    $GLOBAL:_timesToCopy -=1;

    write-host “There are $GLOBAL:_timesToCopy copies left until automatic exit”

    foreach ($file in $GLOBAL:_files) {


        $fileName = $file.Name

        $fullName = $file.FullName

        $fileExists = Test-Path -path (“$drive$fileName”)


        # Note – Copy-Item overwrites files without warning – thus using $fileExists

        if (!($fileExists)) { Copy-Item -Path $fullName -Destination $drive -Recurse}


        # Force parameter required if you are copying a directory and you want to overwrite

        # Files will always be overwritten – so it will still work

        if ($_force) {Copy-Item -Path $fullName -Destination $drive -force -Recurse}


        if ($_promptMode -and $fileExists) {

            write-warning “Will Overwrite: $drive$fileName”

            Copy-Item -Path $fullName -Destination $drive -force -confirm -Recurse



        if ($_promptMode -and (!$fileExists)) {Copy-Item -Path $file -Destination -confirm -Recurse}


            } #End foreach


    } # End $action


# Unregister if we are already subscribed

Unregister-Event newUsbDrive -ErrorAction SilentlyContinue


Register-WmiEvent -Query  $wql -SourceIdentifier “newUsbDrive”  -action $action | out-null


while ($GLOBAL:_timesToCopy -gt 0) {

    # sit and wait for a USB Drive to be inserted

    sleep -Milliseconds 50

    } # While


# Clean up global variable garbage


Remove-Variable _files

Remove-Variable _promptMode

Remove-Variable _timesToCopy

Remove-Variable _force

Remove-Variable _promptMode




} # End

Comments (0)