Powershell - Inexplicable timeout for HttpRequests - powershell

I have a powershell script that monitors several pages on a handful of servers. It gives the HTTP status code from each to determine the server's health.
This has been working perfectly for a number of weeks but today, without making any changes to the script, I started receiving timeout errors ("The operation has timed out"). A telnet on port 80 to the server let me through instantly and I could connect happily via every other mechanism I could think of.
Finally I updated the script by changing the timeout period from 1000ms to 2000ms and the script worked again, instantly. And it still worked after I changed it back to 1000ms.
This is the second time something like this has happened with the same resolution. What is happening and how can I avoid it?

This script may help, it may be a bit overkill for your situation.
function HTTP-Get() {
param
(
[string] $target
)
try
{
$webRequest = [System.Net.WebRequest]::Create($target)
$webRequest.Method = "GET"
[System.Net.WebResponse] $resp = $webRequest.GetResponse();
$rs = $resp.GetResponseStream();
[System.IO.StreamReader] $sr = New-Object System.IO.StreamReader -argumentList $rs;
[string] $results = $sr.ReadToEnd();
}
catch [System.Net.WebException]
{
Write-Warning $_
if ($_.response)
{
$_.response.Close();
}
}
finally
{
if ($sr)
{
$sr.close();
$sr.dispose();
}
if ($resp)
{
$resp.close();
}
}
return $results;
}

As I mentioned in the comments: I had this issue with powershell and none of the .NET http tricks worked (httprequest, webclient). The site I used worked fine with postman and other http helpers.
It seemed like the issue is related to the .NET version, sp, etc. I changed my code to use COM instead of .NET and now it works fine.
......
$HTTPREQUEST_SETCREDENTIALS_FOR_SERVER = 0
$Http = new-object -com "WinHttp.WinHttpRequest.5.1"
$Http.open("GET", "http://....", $false)
$Http.SetCredentials($userName, $password, $HttpREQUEST_SETCREDENTIALS_FOR_SERVER)
$Http.send()
$status = [int]$Http.Status
$responseText = $Http.responseText
.....
I hope it helps :)

Related

Powershell determine new URL of a permanently moved (redirected) resource

I'm using Powershell Core v6-beta.5 using AppImage on Linux. Is there a way to find out the "new" location of a 301 redirect?
Invoke-WebRequest -Method HEAD http://SomethingThatThrows301.com/ -MaximumRedirection 0 throws an error (Response status code does not indicate success: 301 (Moved Permanently)).
While the error does mention that the move is a 301, I'd still like a proper object telling me that, and the new address.
Is there a way to do so?
Note: All code below works in both Windows PowerShell and PowerShell Core, on all supported platforms, with up to 50 redirections by default.
Assuming:
that you don't care about the specific 3xx redirection status code and
that you only need to know the ultimate target URL (there could be a chain of redirections)
use the following:
[System.Net.HttpWebRequest]::Create('http://cnn.com').GetResponse().ResponseUri.AbsoluteUri
This yields (note how the target URL has www.):
http://www.cnn.com
Below is the source code for advanced convenience function Get-UrlRedirection, which packages the functionality, offering both resolution to the ultimate target URL and an enumeration of the chain of redirection URLs.
Example calls:
> Get-UrlRedirection http://cnn.com
http://www.cnn.com
> Get-UrlRedirection -Enumerate http://microsoft.com/about
http://microsoft.com/about
https://microsoft.com/about
https://www.microsoft.com/about
https://www.microsoft.com/about/
https://www.microsoft.com/about/default.aspx
https://www.microsoft.com/en-us/about/
Function Get-UrlRedirection {
[CmdletBinding()]
Param (
[Parameter(Mandatory, ValueFromPipeline)] [Uri] $Url,
[switch] $Enumerate,
[int] $MaxRedirections = 50 # Use same default as [System.Net.HttpWebRequest]
)
process {
try {
if ($Enumerate) { # Enumerate the whole redirection chain, from input URL to ultimate target,
# assuming the max. count of redirects is not exceeded.
# We must walk the chain of redirections one by one.
# If we disallow redirections, .GetResponse() fails and we must examine
# the exception's .Response object to get the redirect target.
$nextUrl = $Url
$urls = #( $nextUrl.AbsoluteUri ) # Start with the input Uri
$ultimateFound = $false
# Note: We add an extra loop iteration so we can determine whether
# the ultimate target URL was reached or not.
foreach($i in 1..$($MaxRedirections+1)) {
Write-Verbose "Examining: $nextUrl"
$request = [System.Net.HttpWebRequest]::Create($nextUrl)
$request.AllowAutoRedirect = $False
try {
$response = $request.GetResponse()
# Note: In .NET *Core* the .GetResponse() for a redirected resource
# with .AllowAutoRedirect -eq $False throws an *exception*.
# We only get here on *Windows*, with the full .NET Framework.
# We either have the ultimate target URL, or a redirection
# whose target URL is reflected in .Headers['Location']
# !! Syntax `.Headers.Location` does NOT work.
$nextUrlStr = $response.Headers['Location']
$response.Close()
# If the ultimate target URL was reached (it was already
# recorded in the previous iteration), and if so, simply exit the loop.
if (-not $nextUrlStr) {
$ultimateFound = $true
break
}
} catch [System.Net.WebException] {
# The presence of a 'Location' header implies that the
# exception must have been triggered by a HTTP redirection
# status code (3xx).
# $_.Exception.Response.StatusCode contains the specific code
# (as an enumeration value that can be case to [int]), if needed.
# !! Syntax `.Headers.Location` does NOT work.
$nextUrlStr = try { $_.Exception.Response.Headers['Location'] } catch {}
# Not being able to get a target URL implies that an unexpected
# error ocurred: re-throw it.
if (-not $nextUrlStr) { Throw }
}
Write-Verbose "Raw target: $nextUrlStr"
if ($nextUrlStr -match '^https?:') { # absolute URL
$nextUrl = $prevUrl = [Uri] $nextUrlStr
} else { # URL without scheme and server component
$nextUrl = $prevUrl = [Uri] ($prevUrl.Scheme + '://' + $prevUrl.Authority + $nextUrlStr)
}
if ($i -le $MaxRedirections) { $urls += $nextUrl.AbsoluteUri }
}
# Output the array of URLs (chain of redirections) as a *single* object.
Write-Output -NoEnumerate $urls
if (-not $ultimateFound) { Write-Warning "Enumeration of $Url redirections ended before reaching the ultimate target." }
} else { # Resolve just to the ultimate target,
# assuming the max. count of redirects is not exceeded.
# Note that .AllowAutoRedirect defaults to $True.
# This will fail, if there are more redirections than the specified
# or default maximum.
$request = [System.Net.HttpWebRequest]::Create($Url)
if ($PSBoundParameters.ContainsKey('MaxRedirections')) {
$request.MaximumAutomaticRedirections = $MaxRedirections
}
$response = $request.GetResponse()
# Output the ultimate target URL.
# If no redirection was involved, this is the same as the input URL.
$response.ResponseUri.AbsoluteUri
$response.Close()
}
} catch {
Write-Error $_ # Report the exception as a non-terminating error.
}
} # process
}
In order to focus on the code, I've omitted the comment-based help above; here it is - simply paste it directly above the function definition:
<#
.SYNOPSIS
Gets a URL's redirection target(s).
.DESCRIPTION
Given a URL, determines its redirection target(s), as indicated by responses
with 3xx HTTP status codes.
If the URL is not redirected, it is output as-is.
By default, the ultimate target URL is determined (if there's a chain of
redirections), but the number of redirections that are followed is limited
to 50 by default, which you may change with -MaxRedirections.
-Enumerate enumerates the redirection chain and returns an array of URLs.
.PARAMETER Url
The URL whose redirection target to determine.
You may supply multiple URLs via the pipeline.
.PARAMETER MaxRedirections
Limits the number of redirections that are followed, 50 by default.
If the limit is exceeded, a non-terminating error is reported.
.PARAMETER Enumerate
Enumerates the chain of redirections, if applicable, starting with
the input URL itself, and outputs it as an array.
If the number of actual redirections doesn't exceed the specified or default
-MaxRedirections value, the entire chain up to the ultimate target URL is
enumerated.
Otherwise, a warning is issued to indicate that the ultimate target URL wasn't
reached.
All URLs are output in absolute form, even if the targets are defined as
relative URLs.
Note that, in order to support multiple input URLs via the pipeline, each
array representing a redirection chain is output as a *single* object, so
with multiple input URLs you'll get an array of arrays as output.
.EXAMPLE
> Get-UrlRedirection http://cnn.com
http://www.cnn.com
.EXAMPLE
> Get-UrlRedirection -Enumerate http://microsoft.com/about
http://microsoft.com/about
https://microsoft.com/about
https://www.microsoft.com/about
https://www.microsoft.com/about/
https://www.microsoft.com/about/default.aspx
https://www.microsoft.com/en-us/about/
.NOTES
This function uses the [System.Net.HttpWebRequest] .NET class and was
inspired by http://www.powershellmagazine.com/2013/01/29/pstip-retrieve-a-redirected-url/
#>
If you ignore the error being thrown, you will be able to examine the HTTP response. The new URL will be in the Location header.
Try something like the below.
$url="https://jigsaw.w3.org/HTTP/300/301.html"
$resp = Invoke-WebRequest -Method HEAD $url -MaximumRedirection 0 -ErrorAction Ignore
$code = $resp.StatusCode
Write-Output "URL: $url"
Write-Output "ErrorCode: $code"
if($code -eq 301) {
$loc = $resp.Headers.Location
Write-Output "New URL: $loc"
}

PowerShell Try/Catch and Retry

I have a fairly large powershell scripts with many (20+) functions which perform various actions.
Right now all of the code doesn't really have any error handling or retry functionality. If a particular task/function fails it just fails and continues on.
I would like to improve error handling and implement retries to make it more robust.
I was thinking something similar to this:
$tries = 0
while ($tries -lt 5) {
try{
# Do Something
# No retries necessary
$tries = 5;
} catch {
# Report the error
# Other error handling
}
}
The problem is that I have many many steps where I would need to do this.
I don't think it make sense to implement the above code 20 times. That seems really superfluous.
I was thinking about writing an "TryCatch" function with a single parameter that contains the actual function I want to call?
I'm not sure that's the right approach either though. Won't I end up with a script that reads something like:
TryCatch "Function1 Parameter1 Parameter2"
TryCatch "Function2 Parameter1 Parameter2"
TryCatch "Function3 Parameter1 Parameter2"
Is there a better way to do this?
If you frequently need code that retries an action a number of times you could wrap your looped try..catch in a function and pass the command in a scriptblock:
function Retry-Command {
[CmdletBinding()]
Param(
[Parameter(Position=0, Mandatory=$true)]
[scriptblock]$ScriptBlock,
[Parameter(Position=1, Mandatory=$false)]
[int]$Maximum = 5,
[Parameter(Position=2, Mandatory=$false)]
[int]$Delay = 100
)
Begin {
$cnt = 0
}
Process {
do {
$cnt++
try {
# If you want messages from the ScriptBlock
# Invoke-Command -Command $ScriptBlock
# Otherwise use this command which won't display underlying script messages
$ScriptBlock.Invoke()
return
} catch {
Write-Error $_.Exception.InnerException.Message -ErrorAction Continue
Start-Sleep -Milliseconds $Delay
}
} while ($cnt -lt $Maximum)
# Throw an error after $Maximum unsuccessful invocations. Doesn't need
# a condition, since the function returns upon successful invocation.
throw 'Execution failed.'
}
}
Invoke the function like this (default is 5 retries):
Retry-Command -ScriptBlock {
# do something
}
or like this (if you need a different amount of retries in some cases):
Retry-Command -ScriptBlock {
# do something
} -Maximum 10
The function could be further improved e.g. by making script termination after $Maximum failed attempts configurable with another parameter, so that you can have have actions that will cause the script to stop when they fail, as well as actions whose failures can be ignored.
I adapted #Victor's answer and added:
parameter for retries
ErrorAction set and restore (or else exceptions do not get caught)
exponential backoff delay (I know the OP didn't ask for this, but I use it)
got rid of VSCode warnings (i.e. replaced sleep with Start-Sleep)
# [Solution with passing a delegate into a function instead of script block](https://stackoverflow.com/a/47712807/)
function Retry()
{
param(
[Parameter(Mandatory=$true)][Action]$action,
[Parameter(Mandatory=$false)][int]$maxAttempts = 3
)
$attempts=1
$ErrorActionPreferenceToRestore = $ErrorActionPreference
$ErrorActionPreference = "Stop"
do
{
try
{
$action.Invoke();
break;
}
catch [Exception]
{
Write-Host $_.Exception.Message
}
# exponential backoff delay
$attempts++
if ($attempts -le $maxAttempts) {
$retryDelaySeconds = [math]::Pow(2, $attempts)
$retryDelaySeconds = $retryDelaySeconds - 1 # Exponential Backoff Max == (2^n)-1
Write-Host("Action failed. Waiting " + $retryDelaySeconds + " seconds before attempt " + $attempts + " of " + $maxAttempts + ".")
Start-Sleep $retryDelaySeconds
}
else {
$ErrorActionPreference = $ErrorActionPreferenceToRestore
Write-Error $_.Exception.Message
}
} while ($attempts -le $maxAttempts)
$ErrorActionPreference = $ErrorActionPreferenceToRestore
}
# function MyFunction($inputArg)
# {
# Throw $inputArg
# }
# #Example of a call:
# Retry({MyFunction "Oh no! It happened again!"})
# Retry {MyFunction "Oh no! It happened again!"} -maxAttempts 10
Solution with passing a delegate into a function instead of script block:
function Retry([Action]$action)
{
$attempts=3
$sleepInSeconds=5
do
{
try
{
$action.Invoke();
break;
}
catch [Exception]
{
Write-Host $_.Exception.Message
}
$attempts--
if ($attempts -gt 0) { sleep $sleepInSeconds }
} while ($attempts -gt 0)
}
function MyFunction($inputArg)
{
Throw $inputArg
}
#Example of a call:
Retry({MyFunction "Oh no! It happend again!"})
Error handling is always going to add more to your script since it usually has to handle many different things. A Try Catch function would probably work best for what you are describing above if you want to have each function have multiple tries. A custom function would allow you to even set things like a sleep timer between tries by passing in a value each time, or to vary how many tries the function will attempt.

Running Access Macro in Powershell

I'm trying to run an Access 2010 macro in PowerShell (v4.0 Windows 8.1) with the below code:
$Access = New-Object -com Access.Application
$Access.OpenCurrentDatabase("SomePath", $False, "Password")
$Access.Run("SomeProc")
$Access.CloseCurrentDatabase()
$Access.Quit()
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($Access)
Remove-Variable Access
I get an error on the line $Access.Run("SomeProc") that there's not enough parameters specified:
Exception calling "Run" with "1" argument(s): "Invalid number of parameters. (Exception
from HRESULT: 0x8002000E (DISP_E_BADPARAMCOUNT))"
The procedure SomeProc does not require any parameters.
I've read the msdn article on the run method and only one parameter is required.
I've also tried this workaround which also failed to work for an unrelated reason.
Does anyone know what the cause of the error could be and how to get the method working?
This is a driver issue where the OLEDB libraries aren't loading correctly.
I was able to reproduce your error exactly, and I was able to work around it by opening Powershell from your SysWow directory instead of System32.
Try opening this version of Powershell (you'll have to run set-executionpolicy again), and see if it'll execute your script.
%SystemRoot%\syswow64\WindowsPowerShell\v1.0\powershell.exe
Helpful link: https://social.msdn.microsoft.com/Forums/en-US/4500877f-0031-426e-869d-bda33d9fe254/microsoftaceoledb120-provider-cannot-be-found-it-may-not-be-properly-installed?forum=adodotnetdataproviders
The C# signature is something like this:
public object Run(string Procedure, ref object Arg1, ... ref object Arg30) ...
It means that COM the Arg optional arguments are not optional in .NET because they are explicitly marked as [ref]. You need to provide all 32 args even if you don't use them.
Assuming you have the following VBA code:
Public Sub Greeting(ByVal strName As String)
MsgBox ("Hello, " & strName & "!"), vbInformation, "Greetings"
End Sub
You can either use call it like this:
$Access = New-Object -com Access.Application
$Access.OpenCurrentDatabase("Database1.accdb")
$runArgs = #([System.Reflection.Missing]::Value) * 31
$runArgs[0] = "Greeting" #Method Name
$runArgs[1] = "Jeno" #First Arg
$Access.GetType().GetMethod("Run").Invoke($Access, $runArgs)
In your case it will be:
$runArgs = #([System.Reflection.Missing]::Value) * 31
$runArgs[0] = "SomeProc"
$Access.GetType().GetMethod("Run").Invoke($Access, $runArgs)
I would probably try to add a helper to the access object:
Add-Member -InputObject $Access -MemberType ScriptMethod -Name "Run2" -Value {
$runArgs = #([System.Reflection.Missing]::Value) * 31
for($i = 0; $i -lt $args.Length; $i++){ $runArgs[$i] = $args[$i] }
$this.GetType().GetMethod("Run").Invoke($this, $runArgs)
}
Then you can use Run2 as you would expect:
$Access.Run2("Greeting", "Jeno")
$Access.Run2("SomeProc")

Using Bitcoin's sendmany function with perl

I have been struggling with this one for a while and will be grateful if someone could offer a solution. Basically, I am trying to use bitcoin's 'sendmany' function (https://en.bitcoin.it/wiki/Original_Bitcoin_client/API_calls_list) to send mass payment with a perl script. I am running bitcoind on a vps and other functions are working fine but sendmany isn't. This is the code that I have :
use Finance::Bitcoin::API;
$ENV{PERL_LWP_SSL_VERIFY_HOSTNAME} = 0;
use Data::Dumper;
my %recipients = ("ADDRESS1" => sprintf("%.8f",0.00005460)+0, "ADDRESS2" => sprintf("%.8f",0.00005460)+0);
my $uri = "https://$rpcuser:$rpcpass\#$rpcip:8332/";
my $api = Finance::Bitcoin::API->new( endpoint => $uri );
my $action = $api->call('sendmany','',%recipients);
if ($api->error)
{
print Dumper($api->error->{message})."\n";
}
else
{
print Dumper($action)."\n";
}
I am able to send single payments using the 'sendtoaddress' function and I am able to use the sendmny function directly in the vps that is running bitcoind by executing it from the shell, but it fails when I try it using the perl script above. There is no error message, I just get the instructions for using sendmany from shell and using curl.
I am also open to scripts in any other language that will let me execute sendmany.
Thanks for the help.
I finally figured out the error in the above code. Replace the line my $action = $api->call('sendmany','',%recipients); with my $action = $api->call('sendmany','',\%recipients);
Basically just add a forward slash before %recipients. Hope this helps someone.

How do I direct a Perl script to check for website response?

I’m pinging a website and checking the availability and sending an email only when it’s down. (That part is working just fine according to the code below.)
require LWP::UserAgent;
my $ua = LWP::UserAgent->new;
$ua->timeout(20);
my $response = $ua->get('https://www.Mysite.net/websuite/');
if (! $response->is_success) {
#print 'CMM Is up and Running';
$path = "C:\\prac\\send_email_failure.ps1";
$pwspath = "c:\\windows\\system32\\windowspowershell\\v1.0\\powershell.exe";
system("$pwspath -command $path"); #using powershell to invoke email utility
}
Now, I’m working on trying to expand the script to see whether
It can check once it’s down and send email (which it’s doing now) and don’t send email until it’s bought up. By the way, I’m using Windows task scheduler to run the script every twenty minutes.
After it sees the website is up it should goto its normal process of checking whether the site is down again and send email (for example the website went down then bought back up and again went down). I’m running the script every 20 mins using task scheduler.
Any help appreciated.
If your script is executed from some kind of scheduler you'll need to persist the status of your last request somehow. You could for example create a file which flags the last status as "down".
Or you could simply run your script as a daemon and schedule a check every 20 minutes (for example with AnyEvent). This way you wouldn't have to cope with filesystem related issues.
use LWP::UserAgent;
use AnyEvent;
my $previous = 1;
my $watch = AnyEvent->timer(interval => 1200, cb => sub {
if(check_status() == 0) {
if($previous == 1) {
# send e-mail
}
$previous = 0;
}
else {
$previous = 1;
}
});
AnyEvent->condvar->recv;
sub check_status {
my $ua = LWP::UserAgent->new(timeout => 20);
my $response = $ua->get('...');
return $response->is_success ? 1 : 0;
}