I want to backup some data over my script. All data shall be zipped in a separate thread. But two things go wrong:
Calling the script results in:
> .\Backup.ps1
outside: -What: Data A -ZipSource -ZipDest C:\Users\xyz\AppData\Local\Temp -Timestamp "20220517-002854
outside: -What: Data B -ZipSource -ZipDest C:\Users\xyz\AppData\Local\Temp -Timestamp "20220517-002854
>
Here is my Script:
class BackupContentData
{
[ValidateNotNullOrEmpty()][string]$What
[ValidateNotNullOrEmpty()][string]$ZipSource
}
$bcd = @(
[BackupContentData]@{ What="Data A"; ZipSource="$env:USERPROFILE\Documents\a_file.txt";}
[BackupContentData]@{ What="Data B"; ZipSource="$env:USERPROFILE\Documents\b_file.txt";}
)
function testFct {
param([string]$What, [string]$ZipSource, [string]$ZipDest, [string]$Timestamp)
Write-Host "inside: -What: "$What" -ZipSource "$ZipSource" -ZipDest "$ZipDest" -Timestamp "$Timestamp
}
$timestamp="$(get-date -f yyyyMMdd-HHmmss)"
foreach ($e in $bcd) {
$job = Start-ThreadJob -Name $e.What -InputObject $e -ScriptBlock {
Invoke-Expression "function getTest {$using:testFct}"
Write-Host "outside: -What: "$input.What" -ZipSource "$input.ZipSource" -ZipDest "$env:Temp" -Timestamp ""$(get-date -f yyyyMMdd-HHmmss)"
getTest -What "$input.What" -ZipSource "$input.ZipSource" -ZipDest "$env:Temp" -Timestamp "$(get-date -f yyyyMMdd-HHmmss)"
}
Receive-Job $job -AutoRemoveJob -Wait
}
What is wrong with the script?
Since testFct
doesn't exist in the scope of your ThreadJob, you need to first store the function's definition and then pass it to the runspace scope and define the function there as demonstrated in this answer.
The other issue is trying to reference the same $input
more than one time. Due to the nature of the automatic variable $input
, you can only reference this variable once in your script block:
Since
$input
is an enumerator, accessing any of its properties causes$input
to no longer be available. You can store$input
in another variable to reuse the$input
properties.
As a workaround you could wrap the variable in the Array subexpression operator @( )
or the Subexpression operator $( )
to store the enumerated output in a new variable.
Here is a simple example of what's explained above:
Start-ThreadJob -InputObject 'Hello World' -ScriptBlock {
"1. $input"
"2. $input"
} | Receive-Job -AutoRemoveJob -Wait
# This outputs:
# 1. Hello World
# 2.
# And the workaround
Start-ThreadJob -InputObject 'Hello World' -ScriptBlock {
# This would also work:
# $thisInput = foreach($i in $input) { $i }
$thisInput = $($input)
"1. $thisInput"
"2. $thisInput"
} | Receive-Job -AutoRemoveJob -Wait
# Outputs:
# 1. Hello World
# 2. Hello World
Lastly, your script is not actually multi-threading, this is because you're storing the job inside your loop and then waiting for it sequentially instead of starting all jobs at once and then waiting for all of them.
class BackupContentData {
[ValidateNotNullOrEmpty()] [string] $What
[ValidateNotNullOrEmpty()] [string] $ZipSource
}
function testFct {
param([string]$What, [string]$ZipSource, [string]$ZipDest, [string]$Timestamp)
Write-Host "inside: -What: $What -ZipSource $ZipSource -ZipDest $ZipDest -Timestamp $Timestamp"
}
# definition of the function is stored here
$def = ${function:testFct}.ToString()
$bcd = @(
[BackupContentData]@{ What="Data A"; ZipSource="$env:USERPROFILE\Documents\a_file.txt" }
[BackupContentData]@{ What="Data B"; ZipSource="$env:USERPROFILE\Documents\b_file.txt" }
)
$job = foreach ($e in $bcd) {
Start-ThreadJob -Name $e.What -InputObject $e -ScriptBlock {
$thisObject = $($input)
# Define a new function with name `getTest` in this scope using `testFct` definition
${function:getTest} = $using:def
Write-Host "outside: -What: $($thisObject.What) -ZipSource $($thisObject.ZipSource) -ZipDest $env:Temp -Timestamp $(get-date -f yyyyMMdd-HHmmss)"
getTest -What $thisObject.What -ZipSource $thisObject.ZipSource -ZipDest $env:Temp -Timestamp (Get-Date -f yyyyMMdd-HHmmss)
}
}
$job | Receive-Job -AutoRemoveJob -Wait
The output you can expect from this:
outside: -What: Data A -ZipSource C:\Users\user\Documents\a_file.txt -ZipDest C:\Users\user\AppData\Local\Temp -Timestamp 20220516-202251
inside: -What: Data A -ZipSource C:\Users\user\Documents\a_file.txt -ZipDest C:\Users\user\AppData\Local\Temp -Timestamp 20220516-202251
outside: -What: Data B -ZipSource C:\Users\user\Documents\b_file.txt -ZipDest C:\Users\user\AppData\Local\Temp -Timestamp 20220516-202251
inside: -What: Data B -ZipSource C:\Users\user\Documents\b_file.txt -ZipDest C:\Users\user\AppData\Local\Temp -Timestamp 20220516-202251