Using the basic construct:
try
{
Do-Something
}
catch
{
Write-Output "Something threw an exception"
}
Is it possible to keep trying the Do-Something until it succeeds? Perhaps using a while loopp like this:
$Timeout = 60
$timer = [Diagnostics.Stopwatch]::StartNew()
$t = 0
while (($timer.Elapsed.TotalSeconds -lt $Timeout) -and ($t -ne 1))) {
Start-Sleep -Seconds 1
try
{
Do-Something
$t = 1
}
catch
{
Write-Output "Something threw an exception"
}
}
$timer.Stop()
Here I use a timer to make sure PowerShell doesn't run indefinitely.
It should keep trying until the try succeeds and $t = 1 is executed. However, it fails in about 2 seconds. Please help.
More specifically the Do-Something is:
(Get-Process -Name FineReader).MainWindowHandle
I'd like the code to keep trying until FineReader exists and it can get the MainWindowHandle.
You can use break keyword.
# Set the erroracton preference to stop when an error occurs,
$ErrorActionPreferenceBak = $ErrorActionPreference
$ErrorActionPreference = 'Stop'
While($True){
try{
Do-Something
break
}
catch{
Write-Output "Something failed"
Start-Sleep -Seconds 1 # wait for a seconds before next attempt.
}
finally{
#Reset the erroracton preference
$ErrorActionPreference = $ErrorActionPreferenceBak
}
}
Your Do-Something should be called with the switch -ErrorAction Stop in order to issue a terminating exception that can be caught by try
For that, you also need to bind your function as a CmdLet. For example:
function DoSomething {
[CmdLetBinding()]
Param(
)
# Your code
}
And then call your function with the -ErrorAction Stop switch:
try {
Do-Something -ErrorAction Stop
}
If your DoSomething is not a function but rather an existing powershell CmdLet, then ... you guessed it, just call it with -ErrorAction Stop
You can learn more about the try/catch/finally in powershell here
Assume that you have a function failing in 9 out of 10 calls:
function Get-RandomFail{
$value = [Random]::new().Next(10);
if ($value -ne 5) { throw }
return $value
}
And you want to limit time window, you can use following:
function Try-Invoke{
[CmdletBinding()]
param(
$Action,
$MaxSeconds,
$Delay=1
)
$timeout = [timespan]::FromSeconds($MaxSeconds)
$start = [DateTime]::Now
do {
try {
return &$Action
} catch {
Write-Verbose "Error"
}
Start-Sleep -Seconds $Delay
} until (([DateTime]::Now - $start) -gt $timeout)
throw
}
$result = Try-Invoke {Get-RandomFail} -MaxSeconds 5 -Verbose
Get-RandomFail will be invoked until no error occurs or until time is over. You can also use Delay argument to modify sleep time after every unsuccesfull Get-RandomFail invocation.
Related
I'm new to powershell and while practicing the examples for loops, I came across a problem that I couldn't solve.
I want to call a notification about the status of a process running in the system through a loop
However, in the code, only the alarm of the process executed first among Taskmgr or Calculator is called by Wait-Process.
I mean, Wait-Process by Sequential Execution makes notification invocation by loop no longer possible
below is my code
do{
Start-Sleep -s 3
$Calculator = Get-Process -Name 'Calculator'
$Taskmgr = Get-Process -Name 'Taskmgr'
if ($Calculator)
{
Show-Notification -ToastTitle 'Calculator is open.'
Wait-Process -name 'Calculator'
Show-Notification -ToastTitle 'Calculator is closed.'
}
if ($Taskmgr)
{
Show-Notification -ToastTitle 'Taskmgr is open.'
Wait-Process -name 'Taskmgr'
Show-Notification -ToastTitle 'Taskmgr is closed.'
}
} while (1 -eq 1)
Since I am Japanese, I am not familiar with English, so I am using a translator. thank you.
I'm still not good enough skill to recreate the code. sorry
Wait-Process blocks the thread indefinitely without the -Timeout parameter but also, while using this parameter if it times out you would get an error and the cmdlet is not particularly useful for this use case.
You could achieve this by using only Get-Process with -ErrorAction SilentlyContinue to avoid errors when the processes are not running and adding else conditions:
while ($true) {
Start-Sleep -s 3
$calc = Get-Process -Name CalculatorApp -ErrorAction SilentlyContinue
$taskmgr = Get-Process -Name Taskmgr -ErrorAction SilentlyContinue
# if calc is running
if ($calc) {
# and there was no notification before
if (-not $calcOpen) {
# display notification
'Calculator is opened.'
# and set a reminder to not display more popups
$calcOpen = $true
}
}
# if calc was not running
else {
# and was running before
if ($calcOpen) {
# display notification
'Calculator is closed.'
# and set a reminder that calc has been closed
$calcOpen = $false
}
}
# same logic applies here
if ($taskmgr) {
if (-not $taskmgrOpen) {
'Taskmgr is opened.'
$taskmgrOpen = $true
}
}
else {
if ($taskmgrOpen) {
'Taskmgr is closed.'
$taskmgrOpen = $false
}
}
}
Background:
This is a parameter validation, its being run on powershell 2.0
there is no option to upgrade the WMF. My problem is...
if both parameters are empty, then display the Specify a parameter, if only P1 is empty display another if P2 is is empty then display another and if both are occupied, display another. I can't find a way to make it work right...
please help
Function checkParam
{
write-verbose "Parameter Validation"
If ("Lzu","Mlv","Irv" -Contains $MoveDBsTo)
{$param1=1}
else{$param1=0}
If (1,2,3 -Contains $SelectedPref)
{$param2=1}
else{$param2=0}
If ($param1 -and $param2 -eq 0)
{
$ErrorParams4 = #{
Message="SYNTAX ERROR: `n Specify a parameter"
Category="InvalidData"
}
Write-error #ErrorParams4
$paramsEmpty=1
Exit
}
If ($param1 -eq 0 -and $param2 -ne 1)
{
$ErrorParams1 = #{
Message="SYNTAX ERROR: `n Check syntax: -MoveDBsto [Lzu, Mlv, Irv]"
Category="InvalidData"
}
$param1=2
Write-error #ErrorParams1
Exit
}
If ($param2 -eq 0 -and $param1 -ne 1)
{
$ErrorParams2 = #{
Message="SYNTAX ERROR: `n Check syntax: -SelectedPref [1,2,3]"
Category="InvalidData"
}
$param2=2
Write-error #ErrorParams2
Exit
}
If ($param1 + $param2 -eq 2)
{
$ErrorParams3 = #{
Message="SYNTAX ERROR: `n Specify only one parameter"
Category="InvalidData"
}
Write-error #ErrorParams3
Exit
}
}
checkParam
I have the following code in my powershell script to validate user input (The first positional argument in the script):
function validatePath {
Param
(
[Parameter(Mandatory=$true)]
[ValidateScript({
If ($_ -match "^([a-z]:\\(?:[-\\w\\.\\d])*)") {
$True
} Else {
Write-Host "Please enter a valid path,$_ is not a valid path."
Write-debug $_.Exception
Break
}
})]
[string]$filePath
)
Process
{
Write-Host "The path is "$filePath
}
}
validatePath -filePath $args[0]
My problem is, currently when the validation fails, and the code goes in the Else block and hits Break, instead of stopping the entire script from continue running, it goes to the next block and everything continues and more errors come out.
Question is, how can I modify my code so that When the validation fails to match the regex, it will throw an appropriate error message and stops the entire script from running?
Set the $ErrorActionPreference variable inside the script to Stop:
$ErrorActionPreference = 'Stop'
function validatePath {
Param
(
[Parameter(Mandatory=$true)]
[ValidateScript({
If ($_ -match "^([a-z]:\\(?:[-\\w\\.\\d])*)") {
$True
} Else {
Write-Host "Please enter a valid path,$_ is not a valid path."
Write-debug $_.Exception
Break
}
})]
[string]$filePath
)
Process
{
Write-Host "The path is "$filePath
}
}
validatePath -filePath $args[0]
Do-MoreStuff # this won't execute if parameter validation fails in the previous call
See the about_Preference_Variables help file for more information
use "exit" to stop the entire script. Put any message before the "exit".
I am trying to test if two PC's are connected by using the following script
$array ='PC1','PC2'
for ($i=0; $i -lt $array.length; $i++) {
Start-Job –Name TestConnection$i –Scriptblock {
if(test-connection $array[$i] -count 1 -quiet){
write-host Success
}
else { write-host No connection
}
}
}
When I try to do Receive-Job for either one I get "Cannot index into a null array".
What am I doing wrong?
You need to pass in the PC name as an argument, as the array does not exist in the context of the script block, like this:
$array ='PC1','PC2'
for ($i=0; $i -lt $array.Length; $i++) {
Start-Job –Name TestConnection –Scriptblock {
param($pcName)
if(Test-Connection $pcName -Count 1 -Quiet) {
Write-Host Success
} else {
Write-Host No connection
}
} -ArgumentList $array[$i]
}
You have to pass $i (and any other variables) via -ArgumentList through the Start-Job Cmdlet since your script block is running in an entirely different powershell host and doesn't have access to anything inside the shell that started the job.
Even though your script block exists inside the original code, Powershell does not expand any variables in it until it's executing the code in the other host. You can define param() at the beginning of your script block to use the variable you pass via -ArgumentList
I found this gui progress bar on a different site. The way that it explains, should solve my problem which when the job is running not let the gui freeze.
However, since I'm dealing with batch files (installing applications) I need to do a foreach app and install them one by one and not let the gui freeze.
Here is the link to the site link
EDIT: UPDATE the SCRIPT. so far this works but all batch files install at the same time which is causing them to fail. I have more applications but for testing I just added 3.
I am only assuming that the $job is not passing its status to "updatescript" and "completedscript"
$Appname = #("Adobe_FlashPlayer", "Acrobat_Reader, "Microsoft_RDP_8.1")
$formJobProgress_Load={
#TODO: Initialize Form Controls here
$timer1.Interval = 1000
$timer1.Tag = 0
$timer1.Start()
[System.Windows.Forms.Application]::DoEvents()
}
$formMain_FormClosed=[System.Windows.Forms.FormClosedEventHandler]{
#Event Argument: $_ = [System.Windows.Forms.FormClosedEventArgs]
#Stop any pending jobs
Stop-JobTracker
}
$timerJobTracker_Tick={
Update-JobTracker
}
#region Job Tracker
$JobTrackerList = New-Object System.Collections.ArrayList
function Add-JobTracker
{
Param (
#[ValidateNotNull()]
#[Parameter(Mandatory = $true)]
[string]$Name,
#[ValidateNotNull()]
#[Parameter(Mandatory = $true)]
[ScriptBlock]$CompletedScript,
[ScriptBlock]$UpdateScript,
[ScriptBlock]$JobScript,
$ArgumentList = $null)
$job = Start-Job -ScriptBlock $JobScript -ArgumentList $ArgumentList
if($job -ne $null)
{
#Create a Custom Object to keep track of the Job & Script Blocks
$psObject = New-Object System.Management.Automation.PSObject
Add-Member -InputObject $psObject -MemberType 'NoteProperty' -Name Job -Value $job
Add-Member -InputObject $psObject -MemberType 'NoteProperty' -Name CompleteScript -Value $CompletedScript
Add-Member -InputObject $psObject -MemberType 'NoteProperty' -Name UpdateScript -Value $UpdateScript
[void]$JobTrackerList.Add($psObject)
#Start the Timer
if(-not $timerJobTracker.Enabled)
{
$timerJobTracker.Start()
}
}
elseif($CompletedScript -ne $null)
{
#Failed
Invoke-Command -ScriptBlock $CompletedScript -ArgumentList $null
}
}
function Update-JobTracker
{
<#
.SYNOPSIS
Checks the status of each job on the list.
#>
#Poll the jobs for status updates
$timerJobTracker.Stop() #Freeze the Timer
for($index =0; $index -lt $JobTrackerList.Count; $index++)
{
$psObject = $JobTrackerList[$index]
if($psObject -ne $null)
{
if($psObject.Job -ne $null)
{
if($psObject.Job.State -ne "Running")
{
#Call the Complete Script Block
if($psObject.CompleteScript -ne $null)
{
#$results = Receive-Job -Job $psObject.Job
Invoke-Command -ScriptBlock $psObject.CompleteScript - ArgumentList $psObject.Job
}
$JobTrackerList.RemoveAt($index)
Remove-Job -Job $psObject.Job
$index-- #Step back so we don't skip a job
}
elseif($psObject.UpdateScript -ne $null)
{
#Call the Update Script Block
Invoke-Command -ScriptBlock $psObject.UpdateScript -ArgumentList $psObject.Job
}
}
}
else
{
$JobTrackerList.RemoveAt($index)
$index-- #Step back so we don't skip a job
}
}
if($JobTrackerList.Count -gt 0)
{
$timerJobTracker.Start()#Resume the timer
}
}
function Stop-JobTracker
{
<#
.SYNOPSIS
Stops and removes all Jobs from the list.
#>
#Stop the timer
$timerJobTracker.Stop()
#Remove all the jobs
while($JobTrackerList.Count -gt 0)
{
$job = $JobTrackerList[0].Job
$JobTrackerList.RemoveAt(0)
Stop-Job $job
Remove-Job $job
}
}#endregion
$buttonStartJob_Click= {
$progressbaroverlay1.Value = 0
$progressbaroverlay1.Step = 1
$progressbaroverlay1.Maximum = $Appname.Count
$this.Enabled = $false
$buttonStartJob.Enabled = $false
#Create a New Job using the Job Tracker
foreach ($app in $Appname)
{
$install = "C:\pstools\Update\cmd\$app\install.cmd"
$run = "C:\Windows\System32\cmd.exe"
Add-JobTracker -Name "test"`
-JobScript {
param (
[string]$batchFilePath
)
Write-Verbose "Launching: [$batchFilePath]" -Verbose
Set-Location $env:windir
& ($run, $batchFilePath)
}` -ArgumentList $install -CompletedScript {
Param ($Job)
#$progressbar1.Value = 100
#Enable the Button
$ProgressBarOverlay1.PerformStep()
$buttonStartJob.ImageIndex = -1
$buttonStartJob.Enabled = $true
}`
-UpdateScript {
Param ($Job)
$results = Receive-Job -Job $Job | Select-Object -Last 1
if ($results -is [int])
{
$progressbaroverlay1.Value = $results
}
}
}
}
$timer1_Tick={
#TODO: Place custom script here
#if ([timespan]::FromSeconds($timerUpdate.Tag) -ge [timespan]::Fromminutes(1))
IF($progressbaroverlay1.Value -eq 100)
{
$timerUpdate.stop()
$formSampleTimer.Close()
}
else
{
[System.Windows.Forms.Application]::DoEvents()
$label1.Text = [timespan]::FromSeconds($timer1.Tag++)
}
}
I am assuming that you either copied this from somewhere and modified it to kind of work for you, or you used something like PowerGUI or Sapien to generate the script for you because this is seriously hard to read code in my opinion.
If I understand it right you create $JobTrackerList as an ArrayList object. Then for each application that you want to install you add a PSCustomObject to that ArrayList with 3 properties, one being a Job that runs in the background, one being a script to run when that is completed, and one being a script to update your progress bar. So each object has a background job that is running, and this is the root issue at this point. But wait, we're not done, you have a timer going on (not actually defined in the code you gave us) that runs, and every 1000 ticks it stops, checks each job, if it is completed it runs the "completed" scriptblock that you defined for that object, and then removes the object from the ArrayList. If it is still not completed it updates the progressbar with the "update" scriptblock. Then it starts the timer again.
Here's what I think needs to happen for this to work like you want... You need to go ahead and make your ArrayList, and add objects to it for each application that you want to run, but do not create jobs for them all! Once you create all the objects start a job for the first application (and add it as a member of the object like you did before). Then when your script checks for completed jobs, before it deletes the object it is looking at it should start the job for the next object.
So, that's going to require a bit of re-write of your code. I'm not sure where to really start with this, because the code that you have is so very different to how I would have written it, so I've given you my suggestions, and now I'll let you run with it from here. If you need help writing the code (at least try first) let me know and I'll see what I can do. Probably not today, I'm dead tired and not on my A game, but I'm sure I can come up with something tomorrow if you end up needing help.
Edit: Ok, I think I have code that should do what you want, and work with what you already have in place:
$formJobProgress_Load={
#TODO: Initialize Form Controls here
$timer1.Interval = 1000
$timer1.Tag = 0
$timer1.Start()
[System.Windows.Forms.Application]::DoEvents()
}
$timerJobTracker_Tick={
$timerJobTracker.Stop() #Freeze the Timer
for($index =0; $index -lt $JobTrackerList.Count; $index++){
$psObject = $JobTrackerList[$index]
if($psObject -ne $null -AND $psObject.Job -ne $null -AND $psObject.Job.State -ne "Running"){
#Perform old 'Complete' scriptblock to update progressbar
$ProgressBarOverlay1.PerformStep()
#Check if this is the only job, and if not start the next one
If($JobTrackerList.count -gt 1){
$NextJob = $JobTrackerList[($index+1)]
$NextJob.Job = Start-Job -ScriptBlock $NextJob.JobScript
}
$JobTrackerList.RemoveAt($index)
Remove-Job -Job $psObject.Job
$index-- #Step back so we don't skip a job
}Else{
$results = Receive-Job -Job $Job | Select-Object -Last 1
if ($results -is [int]){
$progressbaroverlay1.Value = $results
}
}
}
if($JobTrackerList.Count -gt 0){
$timerJobTracker.Start()#Resume the timer
}
}
$formMain_FormClosed=[System.Windows.Forms.FormClosedEventHandler]{
#Stop the timer
$timerJobTracker.Stop()
#Remove all the jobs
while($JobTrackerList.Count -gt 0){
$job = $JobTrackerList[0].Job
$JobTrackerList.RemoveAt(0)
Stop-Job $job
Remove-Job $job
}
}
$Appname = #("Adobe_FlashPlayer", "Acrobat_Reader", "Microsoft_RDP_8.1")
$JobTrackerList = New-Object System.Collections.ArrayList
$buttonStartJob_Click= {
$progressbaroverlay1.Value = 0
$progressbaroverlay1.Step = 1
$progressbaroverlay1.Maximum = $Appname.Count
$this.Enabled = $false
$buttonStartJob.Enabled = $false
ForEach($App in $Appname){
$install = "C:\pstools\Update\cmd\$app\install.cmd"
$run = "C:\Windows\System32\cmd.exe"
$JobScript = {
Write-Verbose "Launching: [$install]" -Verbose
Set-Location $env:windir
& $install
}
[void]$JobTrackerList.Add((New-Object PSObject -Property #{
'Application' = $Name
'JobScript' = $JobScript
'Job' = $null
}))
}
$JobTrackerList[0].Job = Start-Job -ScriptBlock $JobTrackerList[0].JobScript
}
$timer1_Tick={
#TODO: Place custom script here
#if ([timespan]::FromSeconds($timerUpdate.Tag) -ge [timespan]::Fromminutes(1))
IF($progressbaroverlay1.Value -eq 100)
{
$timerUpdate.stop()
$formSampleTimer.Close()
}
else
{
[System.Windows.Forms.Application]::DoEvents()
$label1.Text = [timespan]::FromSeconds($timer1.Tag++)
}
}
I hope that works for you.