"Real" streaming in PowerShell? - powershell

Is there any way to achieve real streaming in PowerShell?
The following statement illustrates what I want:
$(Write-Output "one"; Start-Sleep -Seconds 3; Write-Output "two")
What I'd expect is:
one
<three seconds delay>
two
What actually happens is this:
<three seconds delay>
one
two
So what PowerShell calls "streams" are in fact just lists, but is there some easy way to pass real streams around in PowerShell?
Addition:
I just found out that this does what I'm looking for:
function test {
Write-Output "one";
Start-Sleep -Seconds 3;
Write-Output "two";
}
test | % { Write-Host $_ }
...but why does $( ... ) not behave the same way?

$() executes every statement inside it before passing it's output. You can use ScriptBlocks {} to have output streamed.
To invoke the ScriptBlock in the current scope, use the dot operator.
. {Write-Output "one"; Start-Sleep -Seconds 3; Write-Output "two"}
To invoke it in a child scope, use the & call operator.
& {Write-Output "one"; Start-Sleep -Seconds 3; Write-Output "two"}
You can assign a scriptblock to a variable to pass it around.
function Show-ExecutingScript ($ScriptBlock) {
Write-Host "Executing ScriptBlock: `n$ScriptBlock" -Fore Yellow
& $ScriptBlock
}
$MyCommands = {Write-Output "one"; Start-Sleep -Seconds 3; Write-Output "two"}
Show-ExecutingScript $MyCommands
To learn more about ScriptBlocks, check out about_Script_Blocks

Related

powershell: function call inside a function while procesing them paralelly [duplicate]

Assuming Get-Foo and Get-Foo2 and Deploy-Jobs are 3 functions that are part of a very large module. I would like to use Get-Foo and Get-Foo2 in Deploy-Jobs's Start-ThreadJob (below) without reloading the entire module each time.
Is an working example available for how to do this?
function Deploy-Jobs {
foreach ($Device in $Devices) {
Start-ThreadJob -Name $Device -ThrottleLimit 50 -InitializationScript $initScript -ScriptBlock {
param($Device)
Get-Foo | Get-Foo2 -List
} -ArgumentList $Device | out-null
}
}
The method you can use to pass the function's definition to a different scope is the same for Invoke-Command (when PSRemoting), Start-Job, Start-ThreadJob and ForeEach-Object -Parallel. Since you want to invoke 2 different functions in your job's script block, I don't think -InitializationScript is an option, and even if it is, it might make the code even more complicated than it should be.
You can use this as an example of how you can store 2 function definitions in an array ($def), which is then passed to the scope of each TreadJob, this array is then used to define each function in said scope to be later used by each Job.
function Say-Hello {
"Hello world!"
}
function From-ThreadJob {
param($i)
"From ThreadJob # $i"
}
$def = #(
${function:Say-Hello}.ToString()
${function:From-ThreadJob}.ToString()
)
function Run-Jobs {
param($numerOfJobs, $functionDefinitions)
$jobs = foreach($i in 1..$numerOfJobs) {
Start-ThreadJob -ScriptBlock {
# bring the functions definition to this scope
$helloFunc, $threadJobFunc = $using:functionDefinitions
# define them in this scope
${function:Say-Hello} = $helloFunc
${function:From-ThreadJob} = $threadJobFunc
# sleep random seconds
Start-Sleep (Get-Random -Maximum 10)
# combine the output from both functions
(Say-Hello) + (From-ThreadJob -i $using:i)
}
}
Receive-Job $jobs -AutoRemoveJob -Wait
}
Run-Jobs -numerOfJobs 10 -functionDefinitions $def

Date based foreach loop [duplicate]

I'm working on my first PowerShell script and can't figure the loop out.
I have the following, which will repeat $ActiveCampaigns number of times:
Write-Host "Creating $PQCampaign1 Pre-Qualified Report"
Invoke-Item "$PQCampaignPath1\PQ REPORT $PQCampaign1.qvw"
Write-Host "Waiting 1 minute for QlikView to update"
sleep -seconds 60 # Wait 1 minute for QlikView to Reload, create Report and Save.
DO{
Write-Host "Daily Qlikview Reports"
Write-Host "Wating for QlikView to create the $PQCampaign1 PQ Report"
Get-Date
Write-Host "Checking...."
sleep -seconds 1
Write-Host ""
Write-Host "Not Done Yet"
Write-Host "Will try again in 5 seconds."
Write-Host ""
sleep -seconds 5
}
Until (Test-Path "$PQCampaignPath1\$PQCampaign1 $PQReportName $ReportDate.xlsx" -pathType leaf)
Get-Date
Write-Host "Done with $PQCampaign1 PQ Report. Wait 10 seconds."
sleep -seconds 10
These parameters need to increase with one for each loop:
$PQCampaign1 (should become $PQCampaign2, then 3, etc.)
$PQCampaignPath1 (should become $PQCampaignPath2, then 3, etc.)
So if $ActiveCampaigns is set to 8 on a certain day, then this needs to repeat 8 times and the last time it must open $PQCampaign3 which lies in $PQCampaignPath8.
How can I fix this?
Use:
1..10 | % { write "loop $_" }
Output:
PS D:\temp> 1..10 | % { write "loop $_" }
loop 1
loop 2
loop 3
loop 4
loop 5
loop 6
loop 7
loop 8
loop 9
loop 10
This may be what you are looking for:
for ($i=1; $i -le $ActiveCampaigns; $i++)
{
$PQCampaign = Get-Variable -Name "PQCampaign$i" -ValueOnly
$PQCampaignPath = Get-Variable -Name "PQCampaignPath$i" -ValueOnly
# Do stuff with $PQCampaign and $PQCampaignPath
}
Here is a simple way to loop any number of times in PowerShell.
It is the same as the for loop above, but much easier to understand for newer programmers and scripters. It uses a range and foreach. A range is defined as:
range = lower..upper
or
$range = 1..10
A range can be used directly in a for loop as well, although not the most optimal approach, any performance loss or additional instruction to process would be unnoticeable. The solution is below:
foreach($i in 1..10){
Write-Host $i
}
Or in your case:
$ActiveCampaigns = 10
foreach($i in 1..$ActiveCampaigns)
{
Write-Host $i
If($i==$ActiveCampaigns){
// Do your stuff on the last iteration here
}
}
See this link. It shows you how to dynamically create variables in PowerShell.
Here is the basic idea:
Use New-Variable and Get-Variable,
for ($i=1; $i -le 5; $i++)
{
New-Variable -Name "var$i" -Value $i
Get-Variable -Name "var$i" -ValueOnly
}
(It is taken from the link provided, and I don't take credit for the code.)

Reuse 2 functions in Start-ThreadJob

Assuming Get-Foo and Get-Foo2 and Deploy-Jobs are 3 functions that are part of a very large module. I would like to use Get-Foo and Get-Foo2 in Deploy-Jobs's Start-ThreadJob (below) without reloading the entire module each time.
Is an working example available for how to do this?
function Deploy-Jobs {
foreach ($Device in $Devices) {
Start-ThreadJob -Name $Device -ThrottleLimit 50 -InitializationScript $initScript -ScriptBlock {
param($Device)
Get-Foo | Get-Foo2 -List
} -ArgumentList $Device | out-null
}
}
The method you can use to pass the function's definition to a different scope is the same for Invoke-Command (when PSRemoting), Start-Job, Start-ThreadJob and ForeEach-Object -Parallel. Since you want to invoke 2 different functions in your job's script block, I don't think -InitializationScript is an option, and even if it is, it might make the code even more complicated than it should be.
You can use this as an example of how you can store 2 function definitions in an array ($def), which is then passed to the scope of each TreadJob, this array is then used to define each function in said scope to be later used by each Job.
function Say-Hello {
"Hello world!"
}
function From-ThreadJob {
param($i)
"From ThreadJob # $i"
}
$def = #(
${function:Say-Hello}.ToString()
${function:From-ThreadJob}.ToString()
)
function Run-Jobs {
param($numerOfJobs, $functionDefinitions)
$jobs = foreach($i in 1..$numerOfJobs) {
Start-ThreadJob -ScriptBlock {
# bring the functions definition to this scope
$helloFunc, $threadJobFunc = $using:functionDefinitions
# define them in this scope
${function:Say-Hello} = $helloFunc
${function:From-ThreadJob} = $threadJobFunc
# sleep random seconds
Start-Sleep (Get-Random -Maximum 10)
# combine the output from both functions
(Say-Hello) + (From-ThreadJob -i $using:i)
}
}
Receive-Job $jobs -AutoRemoveJob -Wait
}
Run-Jobs -numerOfJobs 10 -functionDefinitions $def

Powershell loop only if condition is true

Very new to coding in general, so I fear I am missing something completely obvious. I want my program to check for a file. If it is there, just continue the code. If it has not arrived, continue cheking for a given amount of time, or untill the file shows up. My loop works on its own, so when i only select the do-part in Powershell ISE, it works. But when i try running it inside the if statement, nothing happens. The loops doesnt begin.
$exists= Test-Path $resultFile
$a = 1
if ($exists -eq "False")
{
do
{
$a++
log "Now `$a is $a "
start-sleep -s ($a)
$exists= Test-Path $resultFile
write-host "exists = $exists"
}
while (($a -le 5) -and ($exists -ne "True"))
}
Another way of doing this is using a while loop:
$VerbosePreference = 'Continue'
$file = 'S:\myFile.txt'
$maxRetries = 5; $retryCount = 0; $completed = $false
while (-not $completed) {
if (Test-Path -LiteralPath $file) {
Write-Verbose "File '$file' found"
$completed = $true
# Do actions with your file here
}
else {
if ($retryCount -ge $maxRetries) {
throw "Failed finding the file within '$maxRetries' retries"
} else {
Write-Verbose "File not found, retrying in 5 seconds."
Start-Sleep '5'
$retryCount++
}
}
}
Some tips:
Try to avoid Write-Host as it kills puppies and the pipeline (Don Jones). Better would be, if it's meant for viewing the script's progress, to use Write-Verbose.
Try to be consistent in spacing. The longer and more complex your scripts become, the more difficult it will be to read and understand them. Especially when others need to help you. For this reason, proper spacing helps all of us.
Try to use Tab completion in the PowerShell ISE. When you type start and press the TAB-key, it will automatically propose the options available. When you select what you want with the arrow down/up and press enter, it will nicely format the CmdLet to Start-Sleep.
The most important tip of all: keep exploring! The more you try and play with PowerShell, the better you'll get at it.
As pointed out in comments, your problem is that you're comparing a boolean value with the string "False":
$exists -eq "False"
In PowerShell, comparison operators evaluate arguments from left-to-right, and the type of the left-hand argument determines the type of comparison being made.
Since the left-hand argument ($exists) has the type [bool] (a boolean value, it can be $true or $false), PowerShell tries to convert the right-hand argument to a [bool] as well.
PowerShell interprets any non-empty string as $true, so the statement:
$exists -eq "False"
is equivalent to
$exists -eq $true
Which is probably not what you intended.

Loop X number of times

I'm working on my first PowerShell script and can't figure the loop out.
I have the following, which will repeat $ActiveCampaigns number of times:
Write-Host "Creating $PQCampaign1 Pre-Qualified Report"
Invoke-Item "$PQCampaignPath1\PQ REPORT $PQCampaign1.qvw"
Write-Host "Waiting 1 minute for QlikView to update"
sleep -seconds 60 # Wait 1 minute for QlikView to Reload, create Report and Save.
DO{
Write-Host "Daily Qlikview Reports"
Write-Host "Wating for QlikView to create the $PQCampaign1 PQ Report"
Get-Date
Write-Host "Checking...."
sleep -seconds 1
Write-Host ""
Write-Host "Not Done Yet"
Write-Host "Will try again in 5 seconds."
Write-Host ""
sleep -seconds 5
}
Until (Test-Path "$PQCampaignPath1\$PQCampaign1 $PQReportName $ReportDate.xlsx" -pathType leaf)
Get-Date
Write-Host "Done with $PQCampaign1 PQ Report. Wait 10 seconds."
sleep -seconds 10
These parameters need to increase with one for each loop:
$PQCampaign1 (should become $PQCampaign2, then 3, etc.)
$PQCampaignPath1 (should become $PQCampaignPath2, then 3, etc.)
So if $ActiveCampaigns is set to 8 on a certain day, then this needs to repeat 8 times and the last time it must open $PQCampaign3 which lies in $PQCampaignPath8.
How can I fix this?
Use:
1..10 | % { write "loop $_" }
Output:
PS D:\temp> 1..10 | % { write "loop $_" }
loop 1
loop 2
loop 3
loop 4
loop 5
loop 6
loop 7
loop 8
loop 9
loop 10
This may be what you are looking for:
for ($i=1; $i -le $ActiveCampaigns; $i++)
{
$PQCampaign = Get-Variable -Name "PQCampaign$i" -ValueOnly
$PQCampaignPath = Get-Variable -Name "PQCampaignPath$i" -ValueOnly
# Do stuff with $PQCampaign and $PQCampaignPath
}
Here is a simple way to loop any number of times in PowerShell.
It is the same as the for loop above, but much easier to understand for newer programmers and scripters. It uses a range and foreach. A range is defined as:
range = lower..upper
or
$range = 1..10
A range can be used directly in a for loop as well, although not the most optimal approach, any performance loss or additional instruction to process would be unnoticeable. The solution is below:
foreach($i in 1..10){
Write-Host $i
}
Or in your case:
$ActiveCampaigns = 10
foreach($i in 1..$ActiveCampaigns)
{
Write-Host $i
If($i==$ActiveCampaigns){
// Do your stuff on the last iteration here
}
}
See this link. It shows you how to dynamically create variables in PowerShell.
Here is the basic idea:
Use New-Variable and Get-Variable,
for ($i=1; $i -le 5; $i++)
{
New-Variable -Name "var$i" -Value $i
Get-Variable -Name "var$i" -ValueOnly
}
(It is taken from the link provided, and I don't take credit for the code.)