Secret USB copy tool script

Once a guy asked me to build a script which could detect inserted USB disk and save it’s contents to some folder. The job looked easy and fun, so I thought I could share it with everyone.

The first challenge is to detect the USB drive once it is inserted. The best way to do it is using Wait-Event cmdlet. The Wait-Event cmdlet suspends execution of a script or function until a particular event is raised. Execution resumes when the event is detected. So before we use this cmdlet, we should register an event, on which the script would proceed.

There are several ways detecting new devices on a computer. I chose this WMI query:

$Query = "SELECT * FROM Win32_VolumeChangeEvent WHERE EventType = 2 or EventType = 3";

The Win32_VolumeChangeEvent WMI class represents a local drive event that results from the addition of a drive letter or mounted drive on the computer system. I used EventTypes 2 and 3. EventType 2 represents device arrival and EventType 3device removal.

So, once we register an event, we can start waiting for it.

$Query = "SELECT * FROM Win32_VolumeChangeEvent WHERE EventType = 2 or EventType = 3";
Register-WmiEvent -Query $Query -SourceIdentifier USBFlashDrive;
Wait-Event -SourceIdentifier USBFlashDrive;

So, this part let’s us detect if a drive was inserted or removed, but it doesn’t tell us if it’s USB drive. To list all USB drives we can use this:

get-disk | Where-Object {$_.bustype -eq 'usb'}

But this will only tell us the drives that are now present. To make this work we should have an inventory of USB drives before event registration and after event detection. That way we could compare arrays of drives and find differences.

$usbDrives = get-disk | Where-Object {$_.bustype -eq 'usb'};
$Query = "SELECT * FROM Win32_VolumeChangeEvent WHERE EventType = 2 or EventType = 3";
Register-WmiEvent -Query $Query -SourceIdentifier USBFlashDrive;
Wait-Event -SourceIdentifier USBFlashDrive | Out-Null
$newDriveSet = get-disk | Where-Object {$_.bustype -eq 'usb'};
Compare-Object -ReferenceObject $usbDrives -DifferenceObject $newDriveSet -Property SerialNumber;

Overall this comparison looks fine, until you start testing. It’s not working if you do not have any USB drives mounted at the start. Then there’s nothing to compare and cmdlet fails. So we need to write a couple of ifs to make it work.

if($null -eq $usbDrives)
{
$newDriveSerialNumbers = $newDriveSet | Select-Object -ExpandProperty SerialNumber;
$usbDrives = $newDriveSet;
}
else
{
if($null -eq $newDriveSet)
{
$usbDrives = $null;
}
else
{
$newDriveSerialNumbers = Compare-Object -ReferenceObject $usbDrives -DifferenceObject $newDriveSet -Property SerialNumber | Where-Object SideIndicator -eq '=>' | Select-Object -ExpandProperty SerialNumber;
$usbDrives = $newDriveSet;
}
}

Now that we have variable with an array of serial numbers of new drives, we can start copying. But here’s the trick. When the drive is being copied, another USB drive could be inserted, so the copy mechanism should be asynchronous. At this point we will use powershell jobs. And this is really easy! You just write a scriptblock, pass some parameters to it and it works perfectly! So the scriptblock will look like this:

$fileCopyScriptblock = {
$driveLetter = $args[0] + ":\";
$timestamp = (Get-Date).ToString('yyyy-MM-dd-HHmmss');
$copyPath = "D:\usb\$timestamp";
if(!(Test-path -Path "D:\usb"))
{
New-Item -ItemType directory -Path "D:\usb" | out-null
}
Copy-Item -Path $driveLetter -Destination $copyPath -Force -Recurse;
}

We will pass the drive letter to the scriptblock, and the scriptblock will copy all the contents of the USB drive to drive D:, folder named usb.

So when the script comes all together it looks like this:

$usbDrives = get-disk | Where-Object {$_.bustype -eq 'usb'};
$Query = "SELECT * FROM Win32_VolumeChangeEvent WHERE EventType = 2 or EventType = 3";
Register-WmiEvent -Query $Query -SourceIdentifier USBFlashDrive;

#Scriptblock to copy files
$fileCopyScriptblock = {
    $driveLetter = $args[0] + ":\";
    $timestamp = (Get-Date).ToString('yyyy-MM-dd-HHmmss');
    $copyPath = "D:\usb\$timestamp";
    if(!(Test-path -Path "D:\usb"))
    {
        New-Item -ItemType directory -Path "D:\usb" | out-null
    } 
    Copy-Item -Path $driveLetter -Destination $copyPath -Force -Recurse;
}

#Process
while($true)
{
    Wait-Event -SourceIdentifier USBFlashDrive | Out-Null
    Remove-Event -SourceIdentifier USBFlashDrive | Out-Null
    $newDriveSet = get-disk | Where-Object {$_.bustype -eq 'usb'};
    if($null -eq $usbDrives)
    {
        $newDriveSerialNumbers = $newDriveSet | Select-Object -ExpandProperty SerialNumber;
        $usbDrives = $newDriveSet;
    }
    else
    {
        if($null -eq $newDriveSet)
        {
            $usbDrives = $null;
        }
        else
        {
            $newDriveSerialNumbers = Compare-Object -ReferenceObject $usbDrives -DifferenceObject $newDriveSet -Property SerialNumber | Where-Object SideIndicator -eq '=>' | Select-Object -ExpandProperty SerialNumber;
            $usbDrives = $newDriveSet;
        }
    }
    foreach($newDriveSerialNumber in $newDriveSerialNumbers)
    {
        $driveLetter = $usbDrives | Where-Object SerialNumber -eq $newDriveSerialNumber | Get-Partition | Select-Object -ExpandProperty DriveLetter;
        if($driveLetter)
        {
            Start-Job -Name "Copy USB" -ScriptBlock $fileCopyScriptblock -ArgumentList $driveLetter;
        }
    }
}

Now all you need to do is just to schedule script from Scheduler, set it to run as SYSTEM and trigger it on system startup.

Leave a Reply

Your email address will not be published.