powershellpowershell-7.0

Download a file, get the status, then execute the file


I've tried invoke-restmethod, new-object and many other methods to achieve what I'm trying to do. Here are the latest two iterations:

$req = Invoke-WebRequest -uri $scripturl -OutFile "$($scriptpath)\fls.core.ps1" 
Write-Host "StatusCode:" $req.StatusCode

$req = Invoke-WebRequest -uri $scripturl -OutFile "$($scriptpath)\fls.core.ps1" | Select-Object -Expand StatusCode
Write-Host "StatusCode:" $req

Basically I'm attempting to download another PowerShell script and execute it. So obviously it needs to be synchronous. I also need the status so I can determine if it updated or not.

Here is pseudo code for what I'm trying to accomplish:

try {
    download file
} catch {
    output error
    if (local copy exists) {
        log warning that local copy is being used
    } else {
        log error could not download and no local copy available
        exit script
    }
}

run script (only after downloading new one if available)

Here is my current code in full:

$param1=$args[0]
if ($param1 -eq "-d" -or $param1 -eq "-D") { 
    $isDev = $true 
}

#todo: Move to config file
$logpath = "c:\company\logs\loginscript"
$scriptpath = "c:\company\scripts\"
$scripturl = "http://downloads.company.com/fls.core.ps1"
$logfile="$(Get-Date -Format "yyyy-MM-dd hhmmss").log"

Function log($message) {
    Write-Output "[$(Get-Date -Format "yyyy-MM-dd hhmmss")] $message" | Out-file "$($logpath)\$($logfile)" -append
    if ($isDev) { Write-Host "[$(Get-Date -Format "yyyy-MM-dd hhmmss")] $message" }
}

Function createFolder($path) {
    if (-!(Test-Path $path)) { New-Item -Type Directory -Path $path }
}


function updateScripts() {
    try {
        $req = Invoke-WebRequest -uri $scripturl -OutFile "$($scriptpath)\fls.core.ps1" 
        Write-Host "StatusCode:" $req.StatusCode
    } catch {
        Write-Host "StatusCode:" $req.StatusCode
        if ($req.StatusCode -eq 404) {
            log "WARNING: Script not found at $scripturl"
        } else {
            log "ERROR: Script download error: $req.StatusCode"
        }
        if (Test-Path "$($scriptpath)\fls.core.ps1") {
            log "WARNING: Using local script"
        } else {
            log "ERROR: Unable to update script and no local script found. Exiting."
            exit
        }
    }
}

#----------------------------------------------#
#---- MAIN CODE BLOCK -------------------------#
#----------------------------------------------#

createFolder $logpath
createFolder $scriptpath

#update scripts
updateScripts
#execute core loginscript
& $scriptpath/fls.core.ps1

$req.StatusCode appears to be null.


Solution

  • Invoke-WebRequest reports errors as statement-terminating errors, which means that no assignment to variable $req (in statement $req = Invoke-WebRequest ...) takes place in case an error occurs.

    Update:

    Instead, unfortunately, if an error occurs, the response object[1] must be gleaned from the [ErrorRecord] instance representing the error, which is available via $Error[0] after the fact, or via $_ in the catch block of a try { ... } catch { ... } statement (adapted from this answer):

    try {
      Invoke-WebRequest -Uri $scripturl -OutFile "$scriptpath\fls.core.ps1"
    } catch [Microsoft.PowerShell.Commands.HttpResponseException] {
      # Get the status code...
      $statuscode = $_.Exception.Response.StatusCode
      # ... and work with it.
      # if ($statusCode -eq 404) { ...
    } catch {
      # Unexpected error, re-throw
      throw
    }
    

    Strictly speaking, $_.Exception.Response.StatusCode returns a value from an enumeration type, System.Net.HttpStatusCode, not an [int] value, but you can use it like an integer. To return an integer to begin with, append .Value__ or cast to [int].

    Note that Invoke-WebRequest is always synchronous; if you download a file (successfully), the call won't return until the download is completed.


    [1] As the linked answer explains, the response object contained in the error record is of a different type than the one that Invoke-WebRequest returns in case of success (which requires -PassThru if -OutFile is also specified): The error record's .Exception.Response property contains a System.Net.Http.HttpResponseMessage instance, whereas Invoke-WebRequest returns an instance (derived from) Microsoft.PowerShell.Commands.WebResponseObject, which incorporates an instance of the former type, in its .BaseResponse property.