Reputation: 3923
I have a forms-based script that does a lot of file copies, from various sources. The main loop for copying the files can run for a long time and I'd like to add a pause button to hold processing (until the pause button is pressed again)
Following the advice here, I'm using [System.Windows.Forms.Application]::DoEvents()
to keep the form responsive and provide a way to break out the copy loop gracefully if needed.
I'm struggling to see how I can introduce a button to pause the "ForEach" loop instead of breaking it - can anyone point me in the right direction as my attempts to use Do-Until/While seems to hang the script in some way?
A vastly simplified sample of what I'm doing is below:
$StartCopyButton.Add_Click({
$script:CancelLoop = $false
$script:PauseToggle = $false
$CancelButton.Enabled = $true
$StartCopyButton.Enabled = $false
Get-ChildItem -LiteralPath $Source -Recurse -File | ForEach {
Copy-Item -Path $.FullName -Destination $NewDestination
[System.Windows.Forms.Application]::DoEvents()
If($script:CancelLoop -eq $true) {
#Exit the loop
Break;
}
If ($script:PauseToggle) {
Do {
[System.Windows.Forms.Application]::DoEvents()
} Until (!$script:PauseToggle)
}
}
$CancelButton.Enabled = $false
$StartCopyButton.Enabled = $true
})
$CancelButton.Add_Click({
$script:CancelLoop = $true
})
$PauseButton.Add_Click({
# Boolean change value to true/false
$script:PauseToggle = !$script:PauseToggle
})
Upvotes: 1
Views: 1938
Reputation: 17035
Actually, you need only one button for starting & pausing. Here is one very basic approach to illustrate my idea, NOT TESTED:
$script:CancelLoop = $false
$script:PauseLoop = $false
$CopyButton.Add_Click({
# toggle the start/pause state when clicked
$script:PauseLoop = -not $script:PauseLoop
if ($script:PauseLoop) {
$CancelButton.Enabled = $false
$CopyButton.Text = "Start"
}
else {
$CancelButton.Enabled = $true
$CopyButton.Text = "Pause"
# start / resume the loop
Get-ChildItem $Source -Recurse -File | foreach {
$newPath = Join-Path $NewDestination $_.Name
# test if file was already copied
# (might want to compare modified times too)
if (-not (Test-Path $newPath)) {
Copy-Item $_.FullName $newPath
}
[System.Windows.Forms.Application]::DoEvents()
if ($script:CancelLoop -or $script:PauseLoop) {
# exit loop if cancelled or paused
break
}
}
$CancelButton.Enabled = $false
$CopyButton.Text = "Start"
}
})
Another approach would be getting all files first, saving them in a collection, and then store the index and pause/resume at that point. But that bears the risk that the list of files might have changed in the meantime. So a really "safe" solution would be more complex.
Upvotes: 1
Reputation: 1721
You could check the Pause state and if it's true then do empty loop with DoEvents. Like this (didn't tested it though):
$StartCopyButton.Add_Click({
$script:CancelLoop = $false
$script:Pause = $false
$CancelButton.Enabled = $true
$StartCopyButton.Enabled = $false
Get-ChildItem -LiteralPath $Source -Recurse -File | ForEach {
Copy-Item -Path $.FullName -Destination $NewDestination
[System.Windows.Forms.Application]::DoEvents()
while ($script:Pause -and !$script:CancelLoop) {
[System.Windows.Forms.Application]::DoEvents()
sleep 0.1
}
If($script:CancelLoop -eq $true) {
#Exit the loop
Break;
}
}
$CancelButton.Enabled = $false
$StartCopyButton.Enabled = $true
})
$CancelButton.Add_Click({
$script:CancelLoop = $true
})
$PauseButton.Add_Click({
$script:Pause = !$script:Pause
})
Upvotes: 1