# encoding: ascii
# api: powershell
# title: Parse-ApacheRedirects
# description: Parses an Apache redirect file to identify broken entries.
# version: 0.1
# type: script
# author: anonymous
# license: CC0
# function: Invoke-ResourceRequest
# x-poshcode-id: 4076
# x-archived: 2017-03-24T16:02:51
# x-published: 2014-04-05T16:49:00
#
# All redirects will be followed and the final response code will be processed as follows:
# 200 – OK
# 401 – OK
# 403 – Broken
# 404 – Broken
# *** – Optionally remove with -RemoveUnknownErrors switch
# Only Errors are written to the screen which include 403, 404, and unknown errors.
# If more verbosity is desired set the $VerbosePreference variable as follows:
# $VerbosePreference = ‘Continue’
# Written for a Windows 8 system and hasn’t been tested on anything else.
#
<#
.SYNOPSIS
    Parses an Apache redirect file to identify broken entries.
.DESCRIPTION
    All redirects will be followed and the final response code will be processed as follows:
        200 - OK
        401 - OK
        403 - Broken
        404 - Broken
        *** - Optionally remove with -RemoveUnknownErrors switch

    Only Errors are written to the screen which include 403, 404, and unknown errors.
    If more verbosity is desired set the $VerbosePreference variable as follows:
    $VerbosePreference = 'Continue'

    Written for a Windows 8 system and hasn't been tested on anything else.
.PARAMETER RedirectFile
    Location of the Apache redirect file.
.PARAMETER WebsiteDomain
    Domain name of the website that uses the redirect file.
.PARAMETER CreateNewFile
    Will create a new redirect file by appending ".Broken_Removed" to the file name.
    Entries which no longer work will be commented out with "### BROKEN ###"

    Unknown Errors will NOT be removed unless the -RemoveUnknownErrors switch is used.
.PARAMETER RemoveUnknownErrors
    Can only be used if creating a new redirect file as it will 
    comment out Unknown Errors
.EXAMPLE
    PS D:\> .\Parse-ApacheRedirects.ps1 -RedirectFile D:\redirects.conf -WebsiteDomain website.domain.com -CreateNewFile -RemoveUnknownErrors
    Line 2: ERROR Redirect 301 /somefile http://website.domain.com/newlocation.html
    Line 3: ERROR Redirect 301 /anotherfile https://website2.domain.com/
    Line 18: UNKNWN_ERROR: Redirect 301 /foo http://nothing.domain.com/
    Line 18: SVR_RSPND:System.Net.WebException: The remote name could not be resolved: 'nothing.domain.com'
    Line 18: Removing_From_New_Conf

    New file available here:  D:\redirects.conf.Broken_Removed

        Finished
.NOTES
    As mentioned in the Description, setting $VerbosePreference='Continue' will enable
    all Write-Verbose commands in the script.

    **This script was written on a Windows 8 system and hasn't been tested on anything else.**
#>

Param(
    [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][ValidateScript({Test-Path $_})][string]$RedirectFile,
    [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][ValidateScript({Resolve-DnsName $_})][string]$WebsiteDomain,
    [switch]$CreateNewFile,
    [switch]$RemoveUnknownErrors
    )

Function Invoke-ResourceRequest($Uri,[switch]$ReturnSC){
    $RequestedWebsite = "$WebsiteDomain$Uri"
    try{
        $Response = Invoke-WebRequest -Uri $RequestedWebsite
        if($Response.StatusCode -eq 200){ 
            if($ReturnSC){ Return 200 }
            else{ Return 0 }
        }
        else{ Return 1 }
    }
    Catch { 
        [string]$eResponse = $_.Exception.ToString().split("`n")[0]
        if($ReturnSC){ Return $eResponse }
        else{
            # allowing 401 and marking 403 & 404 as failed
            if($eResponse.Contains("(404) Not Found")){ Return 1 }
            elseif($eResponse.Contains("(403) Forbidden")){ Return 1 }
            elseif($eResponse.Contains("(401) Unauthorized")){ Return 0 }
            # all other errors will issue another request to parse error
            else{ Return 2 }
        }
    }
} # End Invoke-ResourceRequest Function

Function Parse-Uri([string]$LineObject,[int]$LineNumber){
    if($LineObject.Contains("`"")){
        #Handle line with space(s)
        $quotes = $LineObject.split("`"").Count
        if($quotes -eq 3){
            #Only one set quoted
            if($LineObject.IndexOf("`"") -gt 13){ 
                Write-Verbose "Line $LineNumber`: Destination_Url_Quoted "
                Return $LineObject.Split(" ")[2]
            }
            else{ 
                Write-Verbose "Line $LineNumber`: Uri_Quoted "
                Return $LineObject.Split("`"")[1]
            }
        }
        elseif($quotes -eq 5){
            Write-Verbose "Line $LineNumber`: Uri_and_Destination_Url_Quoted "
            Return $LineObject.Split("`"")[1]
        }
        else{ 
            Write-Host "Line $LineNumber`: QUOTE_ERROR_REMOVE_RULE" -ForegroundColor Cyan
            Write-Verbose "Line $LineNumber`: Should_Only_Have_2_or_4_Quotes"
            Return ""
        }
    }
    else{
        Write-Verbose "Line $LineNumber`: Normal "
        Return $LineObject.Split(" ")[2]
    }
} # End Parse-Uri Function

# Process input parameters
$RedirectContent = Get-Content -Path $RedirectFile

if($CreateNewFile){ 
    $NewFile = (Get-Item -Path $RedirectFile).FullName+".Broken_Removed" 
    if(Test-Path -Path $NewFile){
        if((Read-Host "New conf file exists. Overwrite? [y/n]").ToLower() -eq "y"){ Clear-Content -Path $NewFile }
        else { 
            Write-Host "`n`tRename $NewFile and launch script again or choose `"y`" to overwrite.`n" 
            Exit
        }
    }
}

if((!($CreateNewFile)) -and ($RemoveUnknownErrors)){
    Write-Host "`n`tMust include the `"CreateNewFile`" switch if removing unknown errors.`n`tExiting script.`n" -ForegroundColor Red
    Exit
}

#Start main logic
$i=1
foreach($line in $RedirectContent){
    [string]$RequestUri = ""
    [int]$Check = 0
    if($line.StartsWith("#")){ Write-Verbose "Line $i`: Comment" }
    else{
        if($line.Contains("Redirect")){
            if($line.Substring(9,1) -eq "3"){
                $RequestUri = Parse-Uri -LineObject $line -LineNumber $i
            }
            else{ # Need ficticious redirect number for parsing
                $RequestUri = Parse-Uri -LineObject $line.tolower().Replace("redirect ","redirect 000 ") -LineNumber $i
            }
            if($RequestUri -eq ""){ $Check = 1 }
        }
        elseif($line.Length -le 3){
            Write-Verbose "Line $i`: Empty"
        }
        else{
            Write-Verbose "Line $i`: NOT_REDIRECT_OR_EMPTY $line"
            Write-Verbose "Line $i`: Rule_Was_NOT_Removed "
        }
    }

    if($RequestUri){ 
        Write-Verbose "Line $i`: Uri_found_making_request"
        $Check = Invoke-ResourceRequest -Uri $RequestUri 
    }

    if($Check -eq 2){ 
        Write-Verbose "Line $i`: Destination_Returned_an_Error "
        Write-Verbose "Line $i`: Making_another_request_to_parse_response "
        $ResponseCode = Invoke-ResourceRequest -Uri $RequestUri -ReturnSC
        Write-Host "Line $i`: UNKNWN_ERROR`: $line" -ForegroundColor Magenta
        Write-Host "Line $i`: SVR_RSPND`:$ResponseCode" -ForegroundColor Magenta
        if($RemoveUnknownErrors -and $CreateNewFile){ 
            Write-Host "Line $i`: Removing_From_New_Conf " -ForegroundColor Magenta
            $Check = 2
        }
        else{ $Check = $0 }
    }
    if($Check -eq 1){ Write-Host "Line $i`: ERROR $line" -ForegroundColor Red }

    if($NewFile -ne $null){
        if($Check -eq 0){ Out-File -FilePath $NewFile -InputObject $line -Append -Encoding ascii }
        else{ Out-File -FilePath $NewFile -InputObject "### BROKEN ### $line" -Append -Encoding ascii }
    }

    $i++
}
if($NewFile){ Write-Host "`nNew file available here: "$NewFile -ForegroundColor Green }
Write-Host "`nFinished`n" -ForegroundColor Green
Exit
