27

Similar to this question here I am trying to monitor if a set of website links are up and running or not responding. I have found the same PowerShell script over the Internet.

However, instead of direct website links I need to check more specific links, for example:

http://mypage.global/Chemical/

http://maypage2:9080/portal/site/hotpot/

When I am trying to check on the status of these links, I get the following output:

URL    StatusCode    StatusDescription    ResponseLength    TimeTaken
http://mypage.global/Chemical/    0
http://maypage2:9080/portal/site/hotpot/    0

The above links requires me to be connected to the VPN, but I can access these links from the browser.

Output of Invoke-WebRequest -Uri https://stackoverflow.com/questions/20259251/powershell-script-to-check-the-status-of-a-url:

PS C:\Users\682126> Invoke-WebRequest -Uri https://stackoverflow.com/questions/20259251/powershell-script-to-check-the-status-of-a-url

The term 'Invoke-WebRequest' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.

At line:1 char:18
+ Invoke-WebRequest <<<<  -Uri https://stackoverflow.com/questions/20259251/powershell-script-to-check-the-status-of-a-url > tmp.txt
    + CategoryInfo          : ObjectNotFound: (Invoke-WebRequest:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException

$PSVersionTable

Name                           Value
----                           -----
CLRVersion                     2.0.50727.5472
BuildVersion                   6.1.7601.17514
PSVersion                      2.0
WSManStackVersion              2.0
PSCompatibleVersions           {1.0, 2.0}
SerializationVersion           1.1.0.1
PSRemotingProtocolVersion      2.1
stackprotector
  • 4,982
  • 4
  • 12
  • 36
debal
  • 887
  • 4
  • 13
  • 28

7 Answers7

74

I recently set up a script that does this.

As David Brabant pointed out, you can use the System.Net.WebRequest class to do an HTTP request.

To check whether it is operational, you should use the following example code:

# First we create the request.
$HTTP_Request = [System.Net.WebRequest]::Create('http://google.com')

# We then get a response from the site.
$HTTP_Response = $HTTP_Request.GetResponse()

# We then get the HTTP code as an integer.
$HTTP_Status = [int]$HTTP_Response.StatusCode

If ($HTTP_Status -eq 200) {
    Write-Host "Site is OK!"
}
Else {
    Write-Host "The Site may be down, please check!"
}

# Finally, we clean up the http request by closing it.
If ($HTTP_Response -eq $null) { } 
Else { $HTTP_Response.Close() }
dWitty
  • 352
  • 5
  • 22
Vasili Syrakis
  • 8,340
  • 1
  • 31
  • 51
  • Thanks, that did the job. But, when the URL is up if i were to display the response time for each URL, how am I to get that value? – debal Dec 05 '13 at 07:22
  • To display the response time, you could wrap the part of your code that does the web request with `(Measure-Command {HTTP REQUEST CODE HERE}).TotalSeconds` – Vasili Syrakis Dec 09 '13 at 01:16
  • 5
    you can also set $HTTP_Request.Method="HEAD" this will give you just header part with no body, will be bit quicker as there is NO BODY being sent – Harshit Aug 26 '14 at 06:45
  • Just checking: What should we be closing here on the last line, `$HTTP_Response` or `$HTTP_Request`? – Ifedi Okonkwo Jun 13 '16 at 06:18
  • `$HTTP_Request`, which is of type `System.Net.HttpWebRequest`, has no member `close()` – Vasili Syrakis Jun 13 '16 at 08:06
  • A few notes: If the URL needs credentials, you need to add `$HTTP_Request.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials`. You need a `Try..Catch` around the `$HTTP_Response = $HTTP_Request.GetResponse()` line, and if that fails, `$HTTP_Response` will be null and so can't be closed because it's already null - like when you get a `(404) Not Found`, you will have no response and error will be `You cannot call a method on a null-valued expression` if you try to do `.Close()` on it. This is a basic framework, but has much to be desired on error handling... – vapcguy Apr 02 '19 at 22:26
  • That last line would then be: `If ($HTTP_Response -eq $null) { } Else { $HTTP_Response.Close() }` – vapcguy Apr 02 '19 at 22:46
  • Its not working for one drive broken link and even below broken link its return status code - 200 : # First we create the request. $HTTP_Request = [System.Net.WebRequest]::Create('https://abc.sharepoint.com/:u:/g/personal/mike_agreeyademo_onmicrosoft_com/EREImDiG7ThLriuR1lIxrAsBojUrbJpGnIY6xyrh6bJdxQ') # We then get a response from the site. $HTTP_Response = $HTTP_Request.GetResponse() # We then get the HTTP code as an integer. $HTTP_Status = [int]$HTTP_Response.StatusCode – RajeshSharepointGeek Jun 09 '20 at 20:11
26

For people that have PowerShell 3 or later (i.e. Windows Server 2012+ or Windows Server 2008 R2 with the Windows Management Framework 4.0 update), you can do this one-liner instead of invoking System.Net.WebRequest:

$statusCode = wget http://stackoverflow.com/questions/20259251/ | % {$_.StatusCode}
Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Warren Rumak
  • 3,666
  • 20
  • 30
  • 20
    Probably worth highlighting that `wget` is an alias for `Invoke-WebRequest` (as is `curl` and `iwr`), I find that it is worth learning the native names in case you need to Google something. – Ray Hayes Apr 04 '15 at 09:01
  • 13
    To continue Ray's comment, I may as well also point out `%` is alias for `ForEach-Object`. Character `$` designates a variable. `_`, when following a `$`, is an alias for `$PSItem`; which is an automatic variable (`man about_Automatic_Variables`) that contains the current object in the pipeline object. Which brings us to the "pipe" `|` (`man about_Pipelines`) and allows the output of the previous command to be used as input for the following command. So the full translation is `$variable = Invoke-WebRequest http://stackoverflow.com/questions/20259251/ | ForEach-Object {$PSItem.StatusCode}` – adam Jul 21 '15 at 19:36
  • 3
    And finally, `.` is a shortcut way to retrieve the property (`man about_Properties`) of an object. So the actual full translation is `$variable = Invoke-WebRequest $url | ForEach-Object {$PSItem | Select-Object -Property StatusCode}` – adam Jul 21 '15 at 19:46
  • +1 for using the easy-to-read universal industry standard name of "wget" instead of the hard to read VB-style Invoke-WebRequest. As we can see from the other comments, the native VB-style gets out of hand really fast (e.g. PSItem / ForEach-Object). – David Betz Dec 08 '15 at 18:23
  • 5
    This actually downloads the file which is very bad. – majkinetor Feb 17 '16 at 07:50
  • 1
    @majkinetor Well yeah, there is no way to tell a remote server to return just the HTTP code and not the content. I suppose such a facility would be useful, but it doesn't exist. – Warren Rumak Mar 27 '16 at 16:14
  • 1
    p.s. Would anyone like to pipe in and identify what `$` and `=` are for, too? – Warren Rumak Mar 27 '16 at 16:19
  • @WarrenRumak, Yes, there is a way without downloading anything. I am not sure I understand your `$` and `=` question but if you ask why do you need them in above code, the answer is - you don't. You can do `wget ...| select -Expand StatusCode` – majkinetor Mar 27 '16 at 16:55
  • That doesn't prevent the actual file transfer, it just discards the result on the client side. The HTTP 1.1 spec does not support returning just the headers of any given request. – Warren Rumak Mar 29 '16 at 20:40
  • .... and before you say "what about HEAD": the implementation of this method is entirely up to the individual server. There is no guarantee that `HEAD /whatever` will return the same status code as `GET /whatever`. – Warren Rumak Mar 29 '16 at 20:48
  • I don't know what specs say but in practice using HEAD method does what it is supposed to do (as you said, perhaps not always but I didn't notice) – majkinetor Apr 01 '16 at 12:06
  • But I checked the specs anyway and it looks like you are wrong: *9.4; HEAD; The HEAD method is identical to GET except that the server MUST NOT return a message-body in the response.* (https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.4) – majkinetor Apr 01 '16 at 12:07
  • That's what the spec says about GET, yes, but I said ANY GIVEN REQUEST for a reason. If you want to check whether a POST, PUT, etc. will work, HEAD won't do that for you. If you want to focus just on GET, fine, but then we're back to the original problem: You have no guarantee that any given server implements HEAD, so it doesn't matter what the spec says anyways. – Warren Rumak Apr 03 '16 at 12:19
  • For what it's worth, if you are in control of the server and it's built with ASP.NET WebApi, you can read Filip Wojcieszyn's article on how to add HEAD support your site. http://www.strathweb.com/2013/03/adding-http-head-support-to-asp-net-web-api/ – Warren Rumak Apr 03 '16 at 12:22
  • 5
    Also see this post: https://www.petri.com/testing-uris-urls-powershell It would suggest the following command: `invoke-webrequest http://stackoverflow.com/questions/20259251/ -DisableKeepAlive -UseBasicParsing -Method head` – W1M0R Nov 04 '16 at 08:25
20

You can try this:

function Get-UrlStatusCode([string] $Url)
{
    try
    {
        (Invoke-WebRequest -Uri $Url -UseBasicParsing -DisableKeepAlive).StatusCode
    }
    catch [Net.WebException]
    {
        [int]$_.Exception.Response.StatusCode
    }
}

$statusCode = Get-UrlStatusCode 'httpstat.us/500'
Rosberg Linhares
  • 2,701
  • 1
  • 25
  • 28
  • 3
    Adding `-Method head` worked better for me, it's faster and avoids downloading... `(Invoke-WebRequest -Uri $Url -UseBasicParsing -DisableKeepAlive -Method head).StatusCode` as used here [https://www.petri.com/testing-uris-urls-powershell](https://www.petri.com/testing-uris-urls-powershell) and [on SO here](https://stackoverflow.com/questions/5353821/webrequest-head-light-weight-alternative) – u8it Sep 08 '17 at 18:34
9
$request = [System.Net.WebRequest]::Create('http://stackoverflow.com/questions/20259251/powershell-script-to-check-the-status-of-a-url')

$response = $request.GetResponse()

$response.StatusCode

$response.Close()
David Brabant
  • 36,511
  • 13
  • 77
  • 101
4

Below is the PowerShell code that I use for basic web URL testing. It includes the ability to accept invalid certs and get detailed information about the results of checking the certificate.

$CertificateValidatorClass = @'
using System;
using System.Collections.Concurrent;
using System.Net;
using System.Security.Cryptography;
using System.Text;

namespace CertificateValidation
{
    public class CertificateValidationResult
    {
        public string Subject { get; internal set; }
        public string Thumbprint { get; internal set; }
        public DateTime Expiration { get; internal set; }
        public DateTime ValidationTime { get; internal set; }
        public bool IsValid { get; internal set; }
        public bool Accepted { get; internal set; }
        public string Message { get; internal set; }

        public CertificateValidationResult()
        {
            ValidationTime = DateTime.UtcNow;
        }
    }

    public static class CertificateValidator
    {
        private static ConcurrentStack<CertificateValidationResult> certificateValidationResults = new ConcurrentStack<CertificateValidationResult>();

        public static CertificateValidationResult[] CertificateValidationResults
        {
            get
            {
                return certificateValidationResults.ToArray();
            }
        }

        public static CertificateValidationResult LastCertificateValidationResult
        {
            get
            {
                CertificateValidationResult lastCertificateValidationResult = null;
                certificateValidationResults.TryPeek(out lastCertificateValidationResult);

                return lastCertificateValidationResult;
            }
        }

        public static bool ServicePointManager_ServerCertificateValidationCallback(object sender, System.Security.Cryptography.X509Certificates.X509Certificate certificate, System.Security.Cryptography.X509Certificates.X509Chain chain, System.Net.Security.SslPolicyErrors sslPolicyErrors)
        {
            StringBuilder certificateValidationMessage = new StringBuilder();
            bool allowCertificate = true;

            if (sslPolicyErrors != System.Net.Security.SslPolicyErrors.None)
            {
                if ((sslPolicyErrors & System.Net.Security.SslPolicyErrors.RemoteCertificateNameMismatch) == System.Net.Security.SslPolicyErrors.RemoteCertificateNameMismatch)
                {
                    certificateValidationMessage.AppendFormat("The remote certificate name does not match.\r\n", certificate.Subject);
                }

                if ((sslPolicyErrors & System.Net.Security.SslPolicyErrors.RemoteCertificateChainErrors) == System.Net.Security.SslPolicyErrors.RemoteCertificateChainErrors)
                {
                    certificateValidationMessage.AppendLine("The certificate chain has the following errors:");
                    foreach (System.Security.Cryptography.X509Certificates.X509ChainStatus chainStatus in chain.ChainStatus)
                    {
                        certificateValidationMessage.AppendFormat("\t{0}", chainStatus.StatusInformation);

                        if (chainStatus.Status == System.Security.Cryptography.X509Certificates.X509ChainStatusFlags.Revoked)
                        {
                            allowCertificate = false;
                        }
                    }
                }

                if ((sslPolicyErrors & System.Net.Security.SslPolicyErrors.RemoteCertificateNotAvailable) == System.Net.Security.SslPolicyErrors.RemoteCertificateNotAvailable)
                {
                    certificateValidationMessage.AppendLine("The remote certificate was not available.");
                    allowCertificate = false;
                }

                System.Console.WriteLine();
            }
            else
            {
                certificateValidationMessage.AppendLine("The remote certificate is valid.");
            }

            CertificateValidationResult certificateValidationResult = new CertificateValidationResult
                {
                    Subject = certificate.Subject,
                    Thumbprint = certificate.GetCertHashString(),
                    Expiration = DateTime.Parse(certificate.GetExpirationDateString()),
                    IsValid = (sslPolicyErrors == System.Net.Security.SslPolicyErrors.None),
                    Accepted = allowCertificate,
                    Message = certificateValidationMessage.ToString()
                };

            certificateValidationResults.Push(certificateValidationResult);
            return allowCertificate;
        }

        public static void SetDebugCertificateValidation()
        {
            ServicePointManager.ServerCertificateValidationCallback = ServicePointManager_ServerCertificateValidationCallback;
        }

        public static void SetDefaultCertificateValidation()
        {
            ServicePointManager.ServerCertificateValidationCallback = null;
        }

        public static void ClearCertificateValidationResults()
        {
            certificateValidationResults.Clear();
        }
    }
}
'@

function Set-CertificateValidationMode
{
    <#
    .SYNOPSIS
    Sets the certificate validation mode.
    .DESCRIPTION
    Set the certificate validation mode to one of three modes with the following behaviors:
        Default -- Performs the .NET default validation of certificates. Certificates are not checked for revocation and will be rejected if invalid.
        CheckRevocationList -- Cerftificate Revocation Lists are checked and certificate will be rejected if revoked or invalid.
        Debug -- Certificate Revocation Lists are checked and revocation will result in rejection. Invalid certificates will be accepted. Certificate validation
                 information is logged and can be retrieved from the certificate handler.
    .EXAMPLE
    Set-CertificateValidationMode Debug
    .PARAMETER Mode
    The mode for certificate validation.
    #>
    [CmdletBinding(SupportsShouldProcess = $false)]
    param
    (
        [Parameter()]
        [ValidateSet('Default', 'CheckRevocationList', 'Debug')]
        [string] $Mode
    )

    begin
    {
        $isValidatorClassLoaded = (([System.AppDomain]::CurrentDomain.GetAssemblies() | ?{ $_.GlobalAssemblyCache -eq $false }) | ?{ $_.DefinedTypes.FullName -contains 'CertificateValidation.CertificateValidator' }) -ne $null

        if ($isValidatorClassLoaded -eq $false)
        {
            Add-Type -TypeDefinition $CertificateValidatorClass
        }
    }

    process
    {
        switch ($Mode)
        {
            'Debug'
            {
                [System.Net.ServicePointManager]::CheckCertificateRevocationList = $true
                [CertificateValidation.CertificateValidator]::SetDebugCertificateValidation()
            }
            'CheckRevocationList'
            {
                [System.Net.ServicePointManager]::CheckCertificateRevocationList = $true
                [CertificateValidation.CertificateValidator]::SetDefaultCertificateValidation()
            }
            'Default'
            {
                [System.Net.ServicePointManager]::CheckCertificateRevocationList = $false
                [CertificateValidation.CertificateValidator]::SetDefaultCertificateValidation()
            }
        }
    }
}

function Clear-CertificateValidationResults
{
    <#
    .SYNOPSIS
    Clears the collection of certificate validation results.
    .DESCRIPTION
    Clears the collection of certificate validation results.
    .EXAMPLE
    Get-CertificateValidationResults
    #>
    [CmdletBinding(SupportsShouldProcess = $false)]
    param()

    begin
    {
        $isValidatorClassLoaded = (([System.AppDomain]::CurrentDomain.GetAssemblies() | ?{ $_.GlobalAssemblyCache -eq $false }) | ?{ $_.DefinedTypes.FullName -contains 'CertificateValidation.CertificateValidator' }) -ne $null

        if ($isValidatorClassLoaded -eq $false)
        {
            Add-Type -TypeDefinition $CertificateValidatorClass
        }
    }

    process
    {
        [CertificateValidation.CertificateValidator]::ClearCertificateValidationResults()
        Sleep -Milliseconds 20
    }
}

function Get-CertificateValidationResults
{
    <#
    .SYNOPSIS
    Gets the certificate validation results for all operations performed in the PowerShell session since the Debug cerificate validation mode was enabled.
    .DESCRIPTION
    Gets the certificate validation results for all operations performed in the PowerShell session since the Debug certificate validation mode was enabled in reverse chronological order.
    .EXAMPLE
    Get-CertificateValidationResults
    #>
    [CmdletBinding(SupportsShouldProcess = $false)]
    param()

    begin
    {
        $isValidatorClassLoaded = (([System.AppDomain]::CurrentDomain.GetAssemblies() | ?{ $_.GlobalAssemblyCache -eq $false }) | ?{ $_.DefinedTypes.FullName -contains 'CertificateValidation.CertificateValidator' }) -ne $null

        if ($isValidatorClassLoaded -eq $false)
        {
            Add-Type -TypeDefinition $CertificateValidatorClass
        }
    }

    process
    {
        return [CertificateValidation.CertificateValidator]::CertificateValidationResults
    }
}

function Test-WebUrl
{
    <#
    .SYNOPSIS
    Tests and reports information about the provided web URL.
    .DESCRIPTION
    Tests a web URL and reports the time taken to get and process the request and response, the HTTP status, and the error message if an error occurred.
    .EXAMPLE
    Test-WebUrl 'http://websitetotest.com/'
    .EXAMPLE
    'https://websitetotest.com/' | Test-WebUrl
    .PARAMETER HostName
    The Hostname to add to the back connection hostnames list.
    .PARAMETER UseDefaultCredentials
    If present the default Windows credential will be used to attempt to authenticate to the URL; otherwise, no credentials will be presented.
    #>
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [Uri] $Url,
        [Parameter()]
        [Microsoft.PowerShell.Commands.WebRequestMethod] $Method = 'Get',
        [Parameter()]
        [switch] $UseDefaultCredentials
    )

    process
    {
        [bool] $succeeded = $false
        [string] $statusCode = $null
        [string] $statusDescription = $null
        [string] $message = $null
        [int] $bytesReceived = 0
        [Timespan] $timeTaken = [Timespan]::Zero 

        $timeTaken = Measure-Command `
            {
                try
                {
                    [Microsoft.PowerShell.Commands.HtmlWebResponseObject] $response = Invoke-WebRequest -UseDefaultCredentials:$UseDefaultCredentials -Method $Method -Uri $Url
                    $succeeded = $true
                    $statusCode = $response.StatusCode.ToString('D')
                    $statusDescription = $response.StatusDescription
                    $bytesReceived = $response.RawContent.Length

                    Write-Verbose "$($Url.ToString()): $($statusCode) $($statusDescription) $($message)"
                }
                catch [System.Net.WebException]
                {
                    $message = $Error[0].Exception.Message
                    [System.Net.HttpWebResponse] $exceptionResponse = $Error[0].Exception.GetBaseException().Response

                    if ($exceptionResponse -ne $null)
                    {
                        $statusCode = $exceptionResponse.StatusCode.ToString('D')
                        $statusDescription = $exceptionResponse.StatusDescription
                        $bytesReceived = $exceptionResponse.ContentLength

                        if ($statusCode -in '401', '403', '404')
                        {
                            $succeeded = $true
                        }
                    }
                    else
                    {
                        Write-Warning "$($Url.ToString()): $($message)"
                    }
                }
            }

        return [PSCustomObject] @{ Url = $Url; Succeeded = $succeeded; BytesReceived = $bytesReceived; TimeTaken = $timeTaken.TotalMilliseconds; StatusCode = $statusCode; StatusDescription = $statusDescription; Message = $message; }
    }
}

Set-CertificateValidationMode Debug
Clear-CertificateValidationResults

Write-Host 'Testing web sites:'
'https://expired.badssl.com/', 'https://wrong.host.badssl.com/', 'https://self-signed.badssl.com/', 'https://untrusted-root.badssl.com/', 'https://revoked.badssl.com/', 'https://pinning-test.badssl.com/', 'https://sha1-intermediate.badssl.com/' | Test-WebUrl | ft -AutoSize

Write-Host 'Certificate validation results (most recent first):'
Get-CertificateValidationResults | ft -AutoSize
JamieSee
  • 11,832
  • 2
  • 28
  • 44
3

You must update the Windows PowerShell to minimum of version 4.0 for the script below to work.


[array]$SiteLinks = "http://mypage.global/Chemical/test.html"
"http://maypage2:9080/portal/site/hotpot/test.json"


foreach($url in $SiteLinks) {

   try {
      Write-host "Verifying $url" -ForegroundColor Yellow
      $checkConnection = Invoke-WebRequest -Uri $url
      if ($checkConnection.StatusCode -eq 200) {
         Write-Host "Connection Verified!" -ForegroundColor Green
      }

   } 
   catch [System.Net.WebException] {
      $exceptionMessage = $Error[0].Exception
      if ($exceptionMessage -match "503") {
         Write-Host "Server Unavaiable" -ForegroundColor Red
      }
      elseif ($exceptionMessage -match "404") {
         Write-Host "Page Not found" -ForegroundColor Red
      }


   }
}

1

For powershell (7) core, this works:

curl -I gourav.io

Output:

HTTP/1.1 308 Permanent Redirect
Date: Tue, 13 Apr 2021 20:29:43 GMT
Content-Type: text/plain
Connection: keep-alive
Location: https://gourav.io/
Refresh: 0;url=https://gourav.io/
server: Vercel
x-vercel-id: bom1::zfh9m-1618345783130-62d01e38e332

OR

Invoke-WebRequest https://gourav.io

Output:

StatusCode        : 200
StatusDescription : OK
Content           : <!DOCTYPE html><html lang="en-US"><head><script async=""
                    src="https://www.googletagmanager.com/gtag/js?id=G-JF3BSQ1LL2"></script><script>
                                window.dataLayer = window.dataLayer || [];
                           …
RawContent        : HTTP/1.1 200 OK
...
...
GorvGoyl
  • 27,835
  • 20
  • 141
  • 143