I am currently working on a script that performs the following tasks:
- Eliminates all folders contained within an uploaded folder.
- Transfers all the files to a different folder.
- Uses ImageMagick to convert all the files into a single .pdf document.
- Relocates the resulting .pdf to another folder and deletes all other files.
Initially, I managed to execute the script without any issues. However, I am now attempting to implement a feature that checks if the folder has finished copying before running the script. Unfortunately, I have encountered a problem whereby the script is executing successfully, but it is doing so on my desktop where the file is stored, rather than within the specified directories. All I am trying to do is ensure that the script waits until the folder has finished copying before proceeding with execution.
My current Code:
$freshStart = 0
$input = 'C:\IT\Convert Drop'
While ($freshStart -eq 0)
{
$status = Get-Item $input | Foreach { $_.LastWriteTime }
If ($status -eq $statusOld)
{
$input = 'C:\IT\Convert Drop'
$output = 'C:\IT\Processing'
$pdf = 'C:\IT\Processing\*.pdf'
$done = 'C:\IT\Converted PDF'
$deleteME = 'C:\IT\Convert Drop\*'
$deleteMEToo = 'C:\IT\Processing\*'
$folder = get-childitem -Path $input -Directory -Name
$fileName = $folder + ".pdf"
Get-ChildItem -Path $input -Recurse -File | Move-Item -Destination $output
& CD $output
& magick "*.{png,jpeg,jpg,tif}" $fileName
Get-ChildItem -Path $pdf -File | Move-Item -Destination $done
Remove-Item $deleteMEToo -Recurse -Force
Remove-Item $deleteME -Recurse -Force
}
Else
{
sleep 10
$statusOld = $status
}
$freshStart = 1
}
### SET FOLDER TO WATCH + FILES TO WATCH + SUBFOLDERS YES/NO
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = "$input"
$watcher.Filter = "*.*"
$watcher.IncludeSubdirectories = $true
$watcher.EnableRaisingEvents = $true
$status = Register-ObjectEvent $watcher "Created"
### DEFINE ACTIONS AFTER AN EVENT IS DETECTED
$action =
{
$status = Get-Item $input | Foreach { $_.LastWriteTime }
If ($status -eq $statusOld)
{
$input = 'C:\IT\Convert Drop'
$output = 'C:\IT\Processing'
$pdf = 'C:\IT\Processing\*.pdf'
$done = 'C:\IT\Converted PDF'
$deleteME = 'C:\IT\Convert Drop\*'
$deleteMEToo = 'C:\IT\Processing\*'
$folder = get-childitem -Path $input -Directory -Name
$fileName = $folder + ".pdf"
Get-ChildItem -Path $input -Recurse -File | Move-Item -Destination $output
#& CD $output
#Set-Location -Path '$output'
& magick "*.{png,jpeg,jpg,tif}" $fileName
Get-ChildItem -Path $pdf -File | Move-Item -Destination $done
Remove-Item $deleteMEToo -Recurse -Force
Remove-Item $deleteME -Recurse -Force
}
Else{
sleep 10
$statusOld = $status
}
}
### DECIDE WHICH EVENTS SHOULD BE WATCHED
Register-ObjectEvent $watcher "Created" -Action $action
while ($true) {sleep 5}
EDITED CODE (Not working):
$freshStart = 0
$inLoc = 'C:\IT\Convert Drop'
$outLoc = 'C:\IT\Processing'
$pdf = 'C:\IT\Processing\*.pdf'
$done = 'C:\IT\Converted PDF'
$deleteME = 'C:\IT\Convert Drop\*'
$deleteMEToo = 'C:\IT\Processing\*'
While ($freshStart -eq 0)
{
$status = Get-Item $inLoc | Foreach { $_.LastWriteTime }
$statusOld = 0
If ($status -eq $statusOld)
{
$folder = get-childitem -Path $inLoc -Directory -Name
$fileName = $folder + ".pdf"
Get-ChildItem -Path $inLoc -Recurse -File | Move-Item -Destination $outLoc
& CD $outLoc
& magick "*.{png,jpeg,jpg,tif}" $fileName
Get-ChildItem -Path $pdf -File | Move-Item -Destination $done
Remove-Item $deleteMEToo -Recurse -Force
Remove-Item $deleteME -Recurse -Force
}
Else
{
$statusOld = $status
$status = Get-Item $inLoc | Foreach { $_.LastWriteTime }
sleep 10
}
$freshStart = 1
}
### SET FOLDER TO WATCH + FILES TO WATCH + SUBFOLDERS YES/NO
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = "$inLoc"
$watcher.Filter = "*.*"
$watcher.IncludeSubdirectories = $true
$watcher.EnableRaisingEvents = $true
### DEFINE ACTIONS AFTER AN EVENT IS DETECTED
$action =
{
$status = Get-Item $inLoc | Foreach { $_.LastWriteTime }
$statusOld = 0
If ($status -eq $statusOld)
{
$folder = get-childitem -Path $inLoc -Directory -Name
$fileName = $folder + ".pdf"
Get-ChildItem -Path $inLoc -Recurse -File | Move-Item -Destination $outLoc
#& CD $outLoc
#Set-Location -Path '$outLoc'
& magick "*.{png,jpeg,jpg,tif}" $fileName
Get-ChildItem -Path $pdf -File | Move-Item -Destination $done
Remove-Item $deleteMEToo -Recurse -Force
Remove-Item $deleteME -Recurse -Force
}
Else{
$statusOld = $status
$status = Get-Item $inLoc | Foreach { $_.LastWriteTime }
sleep 10
}
}
### DECIDE WHICH EVENTS SHOULD BE WATCHED
Register-ObjectEvent $watcher "Created" -Action $action
while ($true) {sleep 5}
UPDATED SCRIPT
#File Locations
$rootPath = 'C:\IT\'
$inLoc = 'Convert Drop'
$prossLoc = 'Processing'
$outLoc = 'Converted PDF'
#File types to include in PDF creation.
$fileTypes = '*.{png,jpeg,jpg,tiff,tif}'
#Function Variables
$inPath = Join-Path -Path "$rootPath" -ChildPath "$inLoc"
$outPath = Join-Path -Path "$rootPath" -ChildPath "$outLoc"
$runPath = Join-Path -Path "$rootPath" -ChildPath "$prossLoc"
$remove1 = Join-Path -Path "$rootPath" -ChildPath "$($inLoc + "\*")"
$remove2 = Join-Path -Path "$rootPath" -ChildPath "$($outLoc + "\*")"
#Folder Watching Variables
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = "$inPath"
$watcher.Filter = "*.*"
$watcher.IncludeSubdirectories = $false
$watcher.EnableRaisingEvents = $true
#Lone Counter
$freshStart = $null
$statusOld = $null
$pathLoc = (Get-Item -Path ".\").FullName
#Pulls the last write time of a folder to compare later.
$grabStatus = {$status = Get-Item $pathLoc | Foreach { $_.LastWriteTime } }
#Get PDF name from Folder
$grabFileName = {
$folder = get-childitem -Path $inPath -Directory -Name
$fileName = $folder + ".pdf"
}
#Move all nested files to single folder.
$moveFiles = {
Get-ChildItem -Path $inPath -Recurse -File | Move-Item -Destination $runPath
}
#Convert Nested files into single PDF
$makePDF = {
& CD $runPath
& magick "$fileTypes" $fileName
}
#Move final PDF
$moveCmplt = {
Get-ChildItem -Path $pdf -File | Move-Item -Destination $outPath
}
#Delete Old files
$deleteOld = {
Remove-Item $remove1 -Recurse -Force
Remove-Item $remove2 -Recurse -Force
}
#Set compare status to current status then fetches new status.
$stats = {
$statusOld = $status
$grabStatus
sleep 10
}
#Exicute main conversion together.
$action = {
$grabStatus
If ($status -eq $statusOld){
$grabFileName
$moveFiles
& CD $runPath
$grabStatus
If ($status -eq $statusOld) {
$makePDF
}
Else{
$stats
}
$deleteOld
}
Else
{
$stats
}
}
#First Time Start, Then Loop run.
While ($freshStart -eq $null) {
If ((Get-ChildItem $inPath | Measure-Object).Count -eq 0) {
}
Else {
$action
}
$freshStart = "FreshStartDone!"
}
#Scan folder every 5 seconds for new content then run convert on change.
Register-ObjectEvent $watcher "Created" -Action $action
while ($true) {sleep 5}
2 Answers
Introduction
When working on a script that involves copying, moving, or deleting files, it’s important to ensure that the script waits until the relevant files are finished copying or moving before executing. This can prevent errors and ensure that the script runs smoothly. In this blog post, we’ll take a look at a specific scenario where a script is not waiting for a folder to finish copying before running, and we’ll explore some possible solutions.
Understanding the Problem
The script in question is designed to take an uploaded folder and remove all its subfolders, dump all of its files into another folder, convert all the files into a single PDF using ImageMagick, move that PDF to another folder, and delete all the other files. The problem is that the script is running on the desktop where the file is stored, rather than in the specified directories. The goal is to ensure that the script waits until the folder is finished copying before running.
The Code
Let’s take a closer look at the relevant code:
$freshStart = 0
$input = 'C:ITConvert Drop'
While ($freshStart -eq 0)
{
$status = Get-Item $input | Foreach { $_.LastWriteTime }
If ($status -eq $statusOld)
{
# Rest of the script
}
Else
{
sleep 10
$statusOld = $status
}
$freshStart = 1
}
### SET FOLDER TO WATCH + FILES TO WATCH + SUBFOLDERS YES/NO
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = "$input"
$watcher.Filter = "*.*"
$watcher.IncludeSubdirectories = $true
$watcher.EnableRaisingEvents = $true
$status = Register-ObjectEvent $watcher "Creat]
At the beginning of the script, the $input variable is set to the path of the folder that needs to be watched. The script then enters a While loop that checks the LastWriteTime of the folder using the Get-Item cmdlet. If the LastWriteTime is the same as the $statusOld variable (which is initially set to $null), the rest of the script is executed. Otherwise, the script sleeps for 10 seconds and sets $statusOld to the current LastWriteTime.
After the While loop, the script sets up a FileSystemWatcher object that will monitor the $input folder for any new files. When a new file is created in the folder (as detected by the Creat event), the script will execute.
Possible Solutions
There are a number of possible solutions to this problem. Let’s explore a few of them.
Using Robocopy
One possible solution is to use Robocopy, a command-line tool that can copy files and folders with a number of advanced options. Robocopy has a “/MON” option that can monitor a directory and execute a command when changes are detected. Here’s an example of how to use Robocopy to monitor a directory for changes and execute a command:
robocopy C:ITConvert Drop C:ITProcessing /MON:1 /MOT:5 /NP /NJH /NJS /NS /NDL /XD *
This command will monitor the “C:ITConvert Drop” directory for changes every 5 minutes (/MOT:5) and execute the command every time a change is detected. The command will copy all files in the directory to “C:ITProcessing” (/NP), without displaying the job header (/NJH), job summary (/NJS), or file list (/NS) and without logging (/NDL). The “/XD *” option excludes all subdirectories.
Using the FileSystemWatcher Object
Another possible solution is to modify the existing script to use the FileSystemWatcher object to monitor the directory for changes instead of the While loop. Here’s an example of how to modify the script:
$input = 'C:ITConvert Drop'
$output = 'C:ITProcessing'
$pdf = 'C:ITProcessing*.pdf'
$done = 'C:ITConverted PDF'
$deleteME = 'C:ITConvert Drop*'
$deleteMEToo = 'C:ITProcessing*'
$folder = get-childitem -Path $input -Directory -Name
$fileName = $folder + ".pdf"
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = $input
$watcher.Filter = "*.*"
$watcher.IncludeSubdirectories = $false
$onCreated = Register-ObjectEvent $watcher Created -Action {
Get-ChildItem -Path $input -Recurse -File | Move-Item -Destination $output
& CD $output
& magick "*.{png,jpeg,jpg,tif}" $fileName
Get-ChildItem -Path $pdf -File | Move-Item -Destination $done
Remove-Item $deleteMEToo -Recurse -Force
Remove-Item $deleteME -Recurse -Force
}
while ($true) {
sleep 5
}
This modified script sets up the same variables for the input, output, PDF, done, deleteME, and deleteMEToo directories, and uses the same code to create the $fileName variable. However, instead of the While loop, the script creates a FileSystemWatcher object that monitors the $input directory for any new files. The $onCreated variable is set to the Register-ObjectEvent cmdlet, which will execute the script block every time a new file is created in the $input directory.
The script then enters an infinite loop that sleeps for 5 seconds, ensuring that the script will continue to run and monitor the directory for changes.
Using Test-Path
Another possible solution is to use the Test-Path cmdlet to check if the directory exists before running the script. Here’s an example of how to use Test-Path:
$input = 'C:ITConvert Drop'
while (!(Test-Path $input)) {
Start-Sleep -Seconds 5
}
$output = 'C:ITProcessing'
$pdf = 'C:ITProcessing*.pdf'
$done = 'C:ITConverted PDF'
$deleteME = 'C:ITConvert Drop*'
$deleteMEToo = 'C:ITProcessing*'
$folder = get-childitem -Path $input -Directory -Name
$fileName = $folder + ".pdf"
Get-ChildItem -Path $input -Recurse -File | Move-Item -Destination $output
& CD $output
& magick "*.{png,jpeg,jpg,tif}" $fileName
Get-ChildItem -Path $pdf -File | Move-Item -Destination $done
Remove-Item $deleteMEToo -Recurse -Force
Remove-Item $deleteME -Recurse -Force
This script uses a While loop that uses the Test-Path cmdlet to check if the $input directory exists. If the directory does not exist, the script will sleep for 5 seconds and try again. Once the directory exists, the script will execute the rest of the code, which is the same as the original script.
Conclusion
When working with scripts that involve copying, moving, or deleting files, it’s important to ensure that the script waits until the relevant files are finished copying or moving before executing. In this blog post, we explored a scenario where a script was not waiting for a folder to finish copying before running, and we explored several possible solutions, including using Robocopy, modifying the script to use the FileSystemWatcher object, and using the Test-Path cmdlet. By using these techniques, you can ensure that your scripts run smoothly and avoid errors.
SOLVED Script
#File Locations
$rootPath = 'C:\IT'
$inLoc = 'Convert Drop'
$prossLoc = 'Processing'
$outLoc = 'Converted PDF'
#File types to include in PDF creation.
$fileTypes = '*.{png,jpeg,jpg,tiff,tif}'
#Function Variables
$inPath = Join-Path -Path "$rootPath" -ChildPath "$inLoc"
$outPath = Join-Path -Path "$rootPath" -ChildPath "$outLoc"
$runPath = Join-Path -Path "$rootPath" -ChildPath "$prossLoc"
$remove1 = Join-Path -Path "$rootPath" -ChildPath "$($inLoc + "\*")"
$remove2 = Join-Path -Path "$rootPath" -ChildPath "$($prossLoc + "\*")"
$global:thefile = Join-Path -Path "$rootPath" -ChildPath "$("START.txt")"
#Lone Vars
$freshStart = $null
$Global:statusOld = $null
$pathLoc = (Get-Item -Path ".\").FullName
$global:allClear = 'No'
$pathTaken = $null
$global:areYouThere = $null
#Pulls the last write time of a folder to compare later.
function grabStatus
{
$global:status = Get-Item $runPath | Foreach { $_.LastWriteTime }
}
#Get PDF name from Folder
function grabFileName
{
$folder = get-childitem -Path $inPath -Directory -Name
$global:fileName = $folder + ".pdf"
}
#Check if Empty
function checkEmpty
{
$directoryInfo = Get-ChildItem $inPath | Measure-Object
$directoryInfo.count #Returns the count of all of the files in the directory
If ($directoryInfo.count -eq 0)
{
$global:allClear = 'Yes'
}
else
{
sleep 5
}
}
#Move all nested files to single folder.
function moveFiles
{
Get-ChildItem -Path $inPath -Recurse -File | Move-Item -Destination $runPath
}
#Convert Nested files into single PDF
function makePDF
{
& CD $runPath
& magick $fileTypes $global:fileName
}
#Move final PDF
function moveCmplt
{
Get-ChildItem -Path "$runPath\*.pdf" -File | Move-Item -Destination $outPath
}
#Delete Old files
function deleteOld
{
Remove-Item $remove1 -Recurse -Force
Remove-Item $remove2 -Recurse -Force
}
#Delete Empty folders
function folderClean
{
do
{
$dirs = gci $inPath -directory -recurse | Where { (gci $_.fullName -Force).count -eq 0 } | select -expandproperty FullName
$dirs | Foreach-Object { Remove-Item $_ }
}
while ($dirs.count -gt 0)
}
#Set compare status to current status then fetches new status.
function stats
{
$Global:statusOld = $Global:status
grabStatus
sleep 10
}
function testPDFcreate
{
do { sleep 5 }
until (test-path -path ($outpath + '\$global:fileName') -eq true)
$global:areYouThere = $true
}
#Exicute main conversion together.
function global:runIt
{
grabFileName
moveFiles
folderClean
Do { checkEmpty }
Until ($global:allClear -eq 'Yes')
makePDF
grabStatus
$global:allClear = 'No'
Do { stats }
Until ($Global:status -eq $Global:statusOld)
moveCmplt
$Global:statusOld = 'No'
If ($global:areYouThere = $true)
{
deleteOld
$global:areYouThere = $false
}
}
while ($true)
{
If ((Get-ChildItem $inPath | Measure-Object).Count -eq 0)
{
If (Test-Path -Path $global:thefile)
{
Remove-Item -Path $global:thefile
}
else{}
}
Else
{
If (Test-Path -Path $global:thefile)
{
[int]$fileSize = "{0:N2}" -f ((Get-ChildItem $inPath -Recurse | Measure-Object -Property Length -Sum -ErrorAction Stop).Sum / 1MB)
If ($fileSize -gt 30)
{
Set-Content -Path $($outPath + "\Please Ask IT to Convert.txt") -Value "Please Ask IT to Convert"
deleteOld
Remove-Item -Path $global:thefile
}
else
{
global:runIt
Remove-Item -Path $global:thefile
}
}
else { sleep 10 }
}
}