Hey, Scripting Guy! How Can I Have Files Copied to a USB Drive When the Drive Is Inserted into a Computer?

Hey, Scripting Guy! How Can I Have Files Copied to a USB Drive When the Drive Is Inserted into a Computer?

  • Comments 1
  • Likes

Bookmark and Share


Hey, Scripting Guy! Question

Hey, Scripting Guy! I would like to be able to use Windows PowerShell 2.0 to monitor for the insertion of a portable USB drive into a computer. When the drive is detected as having been added to the computer, I would like to have the script copy files to the USB drive. Is this possible?

-- AJ

 

Hey, Scripting Guy! AnswerHello AJ,

Microsoft Scripting Guy Ed Wilson here at your service. The 2010 Scripting Games Advanced Event 6 described a scenario that would probably work for your requirements. Expert commentator solutions for this event were posted last week on the Hey, Scripting Guy! Blog. If you would like to see how to perform this task by using VBScript, refer to the expert commentator solutions.

I was rummaging around on PoshCode, the MVP site that hosts the Scripting Games for us, and I ran across an interesting script that was submitted for Advanced Event 6. Let’s take a look at it.

The first thing I notice is that the ADV6_AndrewJarvis.ps1 script uses the Windows PowerShell 2.0 help tags—way to go! Help information tags are discussed in a recent Hey, Scripting Guy! post.

The ADV6_AndrewJarvis.ps1 script will generate an error if it is run without any parameters. This is because the Get-ChildItem cmdlet that is used to retrieve a collection of files for the copy operation requires a path statement. The error is seen in the following image.

Image of error generated when script is run without permissions

There is a problem worse than the fact that the script generates an error: The script is still running. This means that the script will prompt you to copy files to any newly inserted drive, even though there are no files to copy. In addition, when the script is running in the Windows PowerShell ISE, and CTRL+C is used to exit the script when it errors out, WMI eventing is still in effect because the cleanup code did not run.

This is what generated the message seen in orange about not having enough disk space. That message is because a WMI event was triggered but the script itself was not running at the time. Remember, however, that the WMI event collector runs even if the Windows PowerShell script in the ISE is not running at the time. Therefore, when the script was run a second time, the WMI event had been triggered, and the script tried to enter that section of the code. The problem is that the USB drive had already been removed, which created the message about 0 disk space.

The inputs information in the help section tells us that the input needs to be an instance of a System.io.filesysteminfo .NET Framework class. That is the base class, but in reality the input is an instance of a System.Io.FileInfo .NET Framework class. This is important because it tells you how you can provide input to the script. There is one example that shows piping the results of ls to the script. This is excellent practice because it also documents how to use some of the other parameters. One thing that would be better is to provide an example of using each parameter, in addition to different ways of calling some of the more important parameters.

The ls command is an alias for the Get-ChildItem cmdlet. It returns the System.Io.FileInfo .NET Framework class. But the Get-Item cmdlet does as well. This is shown here:

PS C:\fso> (get-item file1.txt).GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True FileInfo System.IO.FileSystemInfo
PS C:\fso> (get-childitem)[0].GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True FileInfo System.IO.FileSystemInfo
PS C:\fso>

To make the –backupitems parameter mandatory, but still allow it to accept piped input, you add mandatory = $true to the parameter statement. You will also notice that the script creates aliases for the parameter. This is shown here:

[parameter(ValueFromPipeline=$true, mandatory=$true)]

[alias("files","folders")]

[System.IO.FileInfo]$BackupItems,

There is a comment in the script in which the author states that the code will get the size of the backed-up items as a job. This comment is not correct because Windows PowerShell jobs are not used in that block of code. If you would like to change the code in that section to use a job, check out these Hey, Scripting Guy! posts on jobs in the getting started series of posts.

I talked about using promptForChoice in a Hey, Scripting Guy! post about error handling. (That article also contains an excellent picture of Lisbon.) The prompt box code is used to create the prompt that is seen when the Adv6_AndrewJarvis.ps1 runs. The output is displayed that is shown in the following image.

Image of output displayed

The heart of the script uses eventing. Windows PowerShell eventing is discussed in a series of Hey, Scripting Guy! posts I have written over the last year of so. The posts talk about the topic from a variety of useful activities.

ADV6_AndrewJarvis.ps1

<#
.Synopsis
Back up provided files/folders to a USB Drive
.Description
When passed the files/folders as a System.IO.FileSystemInfo, this script will copy the files and folders to the requested location
.Parameter BackupItems
The files and folders to be moved to the USB drives
.Parameter Runcount
The number of USB drives to be created; if left at 0, the script will continually run
.Parameter quiet
User will not be prompted to confirm the USB drive is to be used
.Parameter force
Don't prompt for file overwrites
.Parameter whatif
Nothing will be changed, but it will display which items would be effected if run
.Example
ls | create-usbbackup.ps1 -count 2 -verbose
Description
===========
This will move the data returned in the Get-Items query, and prompt the user if a newly added USB drive is to be used (until the user says yes 2 times)
.Inputs
[System.IO.FileSystemInfo[]]
.Outputs
None
.Notes
Name: create-usbbackup.ps1
Author: Andrew Jarvis
Created: 05/03/2010
#>
# ##############################
param(
[parameter(ValueFromPipeline=$true)]
[alias("files","folders")]
[System.IO.FileSystemInfo[]]$BackupItems,
[parameter()]
[alias("count")]
[int]$runCount=0,
[parameter()]
[switch]$quiet,
[parameter()]
[switch]$force,
[parameter()]
[switch]$whatif
)
# ###### BEGIN AKA VAR UNIT & FUCNTIONS #######
BEGIN{
# ###### VAR UNIT ##############
$script:list_o_items = @() #list of files/folders to be backuped.
#date about the backup files (for the record)
$script:backupfile_size = $null
$script:backupfile_count = $null
#information about the volume Identifer
$script:SourceIdentifier_name = "usb_backup_VolumeChange"
#if quiet is true, then we will also be forcing things
if($quiet){$force = $true}
#promptbox
$script:prompt_Continue = New-Object System.Management.Automation.Host.ChoiceDescription "&Copy", "Copy files to this drive."
$script:prompt_cancel = New-Object System.Management.Automation.Host.ChoiceDescription "&Ignore", "Ignore this drive."
$script:options = [System.Management.Automation.Host.ChoiceDescription[]]($prompt_Continue, $prompt_cancel)
}#end Begin
# ####### PROCESS AKA MAIN1 #################
PROCESS{
#only one process, move the items over....
$script:list_o_items += $backupItems
}#end process
# ####### END AKA MAIN2 #####################
END{
write-host "Running Script..."
#get the Size of the Backed items (through a job :))
$tmp_hold = (get-childitem $script:list_o_items -recurse | Measure-object -property length -sum)
$script:backupfile_count = $tmp_hold.count
$script:backupfile_size = $tmp_hold.sum
$tmp_hold = $null
write-verbose $("File Count : $script:backupfile_count")
write-verbose $("File Size : $([int]($script:backupfile_size/1MB)) MB")
Unregister-Event $script:SourceIdentifier_name -ErrorAction SilentlyContinue
Register-WmiEvent -Class win32_VolumeChangeEvent -SourceIdentifier $script:SourceIdentifier_name
$cnt = 0
do{
#wait for the event of the device attaching....
if($runcount -gt 0){write-progress -activity "Backup To USB's" -status "$cnt of $runcount" -percentComplete ($cnt/$runcount*100)}
$Usb_Event = Wait-Event -SourceIdentifier $script:SourceIdentifier_name
#event 2 is the Device Arrival
if ($Usb_Event.SourceEventArgs.NewEvent.EventType -eq 2){
$usb_drive = new-object psobject | select letter, freespace, label
#get info on the usbdrive
$usb_drive.letter = $USB_event.SourceEventArgs.NewEvent.DriveName
$tmp_hold = (get-wmiobject -class win32_logicaldisk -filter "DeviceID = '$($usb_drive.letter)'") #silentlycontinue
$usb_drive.freeSpace = $tmp_hold.FreeSpace
$usb_drive.label = $tmp_hold.VolumeName
$tmp_hold = $null
#now, to continue, we want to make sure there is enough space on this drive
if($usb_drive.freeSpace -gt $script:backupfile_size){
#then we can see about adding it...
$ok_tocopy = $false
if($quiet)
{$ok_tocopy = $true} #if quite is set, then we are just going to move it, otherwise, prompt first
else{
#prompt, then copy
$prompt_answer = $host.ui.PromptForChoice("New USB Drive",`
"Label : $($usb_drive.Label)`n" + `
"Drive letter: $(($usb_drive.letter).TrimEnd(':'))`n" + `
"Free Space : $([INT]($usb_drive.freeSpace/1MB))MB`n" + `
"Do you wish to Copy the files to this Device?", $script:options, 0)
if($prompt_answer -eq 0){$ok_tocopy = $true}
}#end if quiet
#moving on
if($ok_tocopy){
#runcount 0 means just keep running, but if it's greater then 0,
#it means we have a set run time, and we want to increase the counter
if($runcount -gt 0){$cnt++}
$item_count = 0
foreach($item in $script:list_o_items){
$item_count ++
write-progress -activity "FileCopy" -status "Progress: $($item.name)" -percentComplete ($item_count/$script:list_o_items.count*100)
Try{
copy-item -path $($item.fullname) -destination $($usb_drive.letter) -Recurse -force:$force -whatif:$whatif
Write-Verbose "Copied $($item.name) to $($usb_drive.letter)"
}catch{
write-Warning "Unable to move file"
}#end try
}#next item
write-host "Copy Complete..."
}#end ok to copy
}else{
#there isn't enought space to add drive,
write-warning $("$($usb_drive.letter) drive does not have enought space. Drive Size: $([INT]($usb_drive.size/1MB))MB Free space")
}#end if
}#end if USB event it device arival
Remove-Event -SourceIdentifier $script:SourceIdentifier_name
} while (($cnt -lt $runcount) -or ($runcount -eq 0)) #Loop until next event
# #######clean up#########
Unregister-Event $script:SourceIdentifier_name
write-host "Ending Script..."
}#end end
# ###### END ###################


AJ, that is all there is to using Windows PowerShell to copy files to a USB drive when it is inserted. The 2010 Scripting Games wrap up week 2 will continue tomorrow.

 

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


Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • Hi,

    I was looking for a similar code to automatically connect to a server that scans the usb stick for viruses when inserted. Is it possible through the script to give orders to the client machine not to open the usbstick before being scanned  by the server?