I have always been a great fan of a tool named RoboCopy, which I bet many of you have used countless times. Now these days I have been in need for this very same type of robustness for one of my functions since I am running it in an unreliable environment.
What I wanted to achieve with PowerShell in particular, was to fetch a great deal of data from public web servers and reuse the information in my scripts. Unfortunately when you use a cmdlet, be it Invoke-RestMethod, or Test-Connection, you can get failures which are not due to your cmdlets but to the underlying infrastructure (such as a Wi-Fi network, a distorted topology because of a flapping router or a way too busy web server).
Sure Invoke-RestMethod has a TimeoutSec parameter, but what if it fails and I really need the information coming from that website? Well, this reasoning brought me to write an advanced function that takes a command and its parameters and tries to run it a given number of times (three by default) with intervals of three seconds.
This function, which I called Start-RoboCommand (Start is an approved verb, so that my PSScriptAnalyzer is happy, and I borrowed the idea of the RoboCommand noun from RoboCopy itself), also supports an improved functioning where the command is run indefinitely, through the addition of a -Wait parameter, such as the one you can find in recent versions of Get-Content.
To finish with, I added a LogFile parameter to log errors (which is particularly important here because we are exactly dealing with commands not being successfull) and Verbose, which tells you exactly what's going wrong.
Now without further ado, here's my function:
function Start-RoboCommand { <# .Synopsis Function that tries to run a command until it succeeds or forever .DESCRIPTION Function that tries to run a command until it succeeds or forever. By default this function tries to run a command three times with three seconds intervals. .PARAMETER Command Command to execute .PARAMETER Args Arguments to pass to the command .PARAMETER Count Number of tries before throwing an error .PARAMETER Wait Run the command forever even if it succeeds .PARAMETER Delay Time in seconds between two tries .PARAMETER LogFile The path to the error log .EXAMPLE Start-RoboCommand -Command 'Invoke-RestMethod' -Args @{ URI = "http://guid.it/json"; TimeoutSec = 1 } -Count 2 -Verbose .EXAMPLE Start-RoboCommand -Command 'Invoke-RestMethod' -Args @{ URI = "http://notexisting.it/json"; TimeoutSec = 1 } -Count 2 -Verbose .EXAMPLE Start-RoboCommand -Command 'Invoke-RestMethod' -Args @{ URI = "http://guid.it/json"; TimeoutSec = 1 } -Wait -Verbose .EXAMPLE Start-RoboCommand -Command 'Invoke-RestMethod' -Args @{ URI = "http://notexisting.it/json"; TimeoutSec = 1 } -Wait -Verbose .EXAMPLE Start-RoboCommand -Command 'Test-Connection' -Args @{ ComputerName = "bing.it" } -Wait -Verbose .EXAMPLE Start-RoboCommand -Command 'Test-Connection' -Args @{ ComputerName = "nocomputer" } -Wait -LogFile $Env:temp\error.log -Verbose .EXAMPLE Start-RoboCommand -Command Get-Content -Args @{path='d:\inputfile.txt'} -Wait -DelaySec 2 -LogFile $Env:temp\error.log -Verbose .NOTES happysysadm.com @sysadm2010 #> [CmdletBinding(SupportsShouldProcess,DefaultParameterSetName='Limited')] Param ( [Parameter(Mandatory=$true)] [Alias("Cmd")] [string]$Command, [Parameter(Mandatory=$true)] [hashtable]$Args, [Parameter(Mandatory=$false,ParameterSetName = 'Limited')] [int32]$Count = 3, [Parameter(Mandatory=$false,ParameterSetName = 'Forever')] [switch]$Wait, [Parameter(Mandatory=$false)] [int32]$DelaySec = 3, [Parameter(Mandatory=$false)] $LogFile ) $Args.ErrorAction = "Stop" $RetryCount = 0 $Success = $false do { try { & $command @args Write-Verbose "$(Get-Date) - Command $Command with arguments `"$($Args.values[0])`" succeeded." if(!$Wait) { $Success = $true } } catch { if($LogFile) { "$(Get-Date) - Error: $($_.Exception.Message) - Command: $Command - Arguments: $($Args.values[0])" | Out-File $LogFile -Append } if ($retrycount -ge $Count) { Write-Verbose "$(Get-Date) - Command $Command with arguments `"$($Args.values[0])`" failed $RetryCount times. Exiting." $PSCmdlet.ThrowTerminatingError($_) } else { Write-Verbose "$(Get-Date) - Command $Command with arguments `"$($Args.values[0])`" failed. Retrying in $DelaySec seconds." Start-Sleep -Seconds $DelaySec if(!$Wait) { $RetryCount++ } } } } while (!$Success) }
Let me know how it works for you and if you have any suggestion on the logic I'll be more than happy to improve it over time. Fo sure you can find it also on my github.
No comments:
Post a Comment