Below is my Powershell environment (via the Get-Host command):
Name : Windows PowerShell ISE Host
Version : 5.1.15063.608
InstanceId : 46c51405-fc6d-4e36-a2ae-09dbd4069710
UI : System.Management.Automation.Internal.Host.InternalHostUserInterface
CurrentCulture : en-US
CurrentUICulture : en-US
PrivateData : Microsoft.PowerShell.Host.ISE.ISEOptions
DebuggerEnabled : True
IsRunspacePushed : False
Runspace : System.Management.Automation.Runspaces.LocalRunspace
I have a script that makes a REST request to a resource -- everything works fine.
While testing, I changed the URL to be invalid to see if error messages are written to the output file -- this is where I am confused. It appears that the only way to write exceptions to the log is by using Write-Host. When I use Write-Output the exception messages are NEVER written to the log file. Below is the code I am using:
function rest_request( $method, $uri )
{
# Code here create #req dictionary
try {
$json = Invoke-RestMethod #req
} catch {
# If using Write-Output - Nothing is written to output file???
# MUST use Write-Host for error info to be included in output file - WHY?
Write-Host "* REST Request Error:"
Write-Host $error[0] | Format-List -force
$json = $null
}
return $json
}
function do_something()
{
Write-Output "Start..."
# other functions called here but removed for clarity.
# The other functions contain Write-Output statements
# which are showing in the log file.
# Issue REST request with invalid URL to cause exception ...
$json = rest_request "Get" "https://nowhere.com/rest/api"
Write-Output "Done."
}
do_something | Out-File "D:\temp\log_file.txt" -Append
According to this blog post: using Write-Host should be avoided, and yet, it seems that Write-Host is the only way I can get error messages in the output file.
My question is: How do you write a function that makes a REST request and returns the result if successful, but if an error occurs, write the error message to an output file and return $null?
As I am fairly green when it comes to PowerShell, any advice would be appreciated.
Ok - I gave up completely on redirection and instead wrote a function that writes the parameter to the log file, as follows:
function log_write
{
param
(
[Parameter(ValueFromPipeline=$true)] $piped
)
$piped | Out-File $log_file -Append
}
Note: $log_file is set at initialization as I needed a new log file name for each day of the week.
Then, I replaced ALL Write-Output / Write-Host with log_write, i.e.:
log_write "* REST Request Error:"
$s = $error[0] | Format-List -force
log_write $s
Works like a charm and exceptions messages are written to the log file without any of the previous issues.
As far as I know Write-Host only writes to the host, nothing else.
In your example, do_something does not return anything, so it's no wonder you don't get anything in the log file.
Here is how you could do it, but many different approaches exist:
function Send-RESTRequest {
Param($method, $uri, $logFile)
# Code here create #req dictionary
try {
# make sure exception will be caught
$json = Invoke-RestMethod #req -ErrorAction Stop
} catch {
# send exception details to log file
$_ | Out-File $logFile -Append
$json = $null
}
return $json
}
function Do-Something {
# Issue REST request with invalid URL to cause exception ...
$json = Send-RESTRequest -method "Get" -uri "https://nowhere.com/rest/api" -logFile "D:\temp\log_file.txt"
}
Do-Something
Related
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.
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.
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.
I have a URL health-checking PowerShell script which correctly gets an HTTP 200 status code on most of my intranet sites, but a '0' status code is returned on a small minority of them. The '0' code is an API return rather than from the web site itself, according to my research of questions from others who have written similar URL-checking PowerShell scripts. Thinking this must be a timeout issue, where API returns '0' before the slowly-responding web site returns its 200, I've researched yet more questions about this subject area on SO and implemented a suggestion from someone to insert a timeout in the script. The timeout setting though, no matter how high I set the timeout value, doesn't help. I still get the same '0' "response" code from the same web sites even though those web sites are up and running as checked from any regular web browser. Any thoughts on how I could tweak the timeout setting in the script below in order to get the correct 200 response code?
The Script:
$URLListFile = "C:\Users\Admin1\Documents\Scripts\URL Check\URL_Check.txt"
$URLList = Get-Content $URLListFile -ErrorAction SilentlyContinue
#if((test-path $reportpath) -like $false)
#{
#new-item $reportpath -type file
#}
#For every URL in the list
$result = foreach($Uri in $URLList) {
try{
#For proxy systems
[System.Net.WebRequest]::DefaultWebProxy = [System.Net.WebRequest]::GetSystemWebProxy()
[System.Net.WebRequest]::DefaultWebProxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials
#Web request
$req = [system.Net.WebRequest]::Create($uri)
$req.Timeout=5000
$res = $req.GetResponse()
}
catch {
#Err handling
$res = $_.Exception.Response
}
$req = $null
#Getting HTTP status code
$int = [int]$res.StatusCode
# output a formatted string to capture in variable $result
"$int - $uri"
#Disposing response if available
if($res){
$res.Dispose()
}
}
# output on screen
$result
#output to log file
$result | Set-Content -Path "C:\Users\Admin1\Documents\Scripts\z_Logs\URL_Check\URL_Check_log.txt" -Force
Current output:
200 - http://192.168.1.1/
200 - http://192.168.1.2/
200 - http://192.168.1.250/config/authentication_page.htm
0 - https://192.168.1.50/
200 - http://app1-vip-http.dev.local/
0 - https://CA/certsrv/Default.asp
Perhaps using PowerShell cmdlet Invoke-WebRequest works better for you. It has many more parameters and switches to play around with like ProxyUseDefaultCredentials and DisableKeepAlive
$pathIn = "C:\Users\Admin1\Documents\Scripts\URL Check\URL_Check.txt"
$pathOut = "C:\Users\Admin1\Documents\Scripts\z_Logs\URL_Check\URL_Check_log.txt"
$URLList = Get-Content -Path $pathIn
$result = foreach ($uri in $URLList) {
try{
$res = Invoke-WebRequest -Uri $uri -UseDefaultCredentials -UseBasicParsing -Method Head -TimeoutSec 5 -ErrorAction Stop
$status = [int]$res.StatusCode
}
catch {
$status = [int]$_.Exception.Response.StatusCode.value__
}
# output a formatted string to capture in variable $result
"$status - $uri"
}
# output on screen
$result
#output to log file
$result | Set-Content -Path $pathOut -Force
I am invoking a lot of REST calls in a function.
I know that some of them will fail, but that is expected.
My question is:
How do I prevent powershell from adding entries to the global $error variable?
foreach:
$oldErrorActionPreference = $ErrorActionPreference
$ErrorActionPreference = "Ignore"
try {
$response = Invoke-RestMethod -Uri $Uri -ea Ignore
} catch {
Write-Verbose "$_"
} finally {
$ErrorActionPreference = $oldErrorActionPreference
}
$error variable after invoking:
Invoke-RestMethod cmdlet always fails with terminating error, which can't be Ignored — it will always end up inside $Error list.
You can clear it: $Error.Clear()
I have a list of URL in a text file and I want to test whether all of them are reachable or not. I fired the following command in windows powershell but somehow after displaying the status of first two requests, the command stucks somewhere and never returns. Am I missing something?
cat .\Test.txt | % { [system.Net.WebRequest]::Create("$_").GetResponse().StatusCode }
Text File
http://www.google.com
http://www.yahoo.com
http://www.bing.com
Output:
OK
OK
----> after that it stucks.
use Invoke-WebRequest instead:
$sites = 'http://www.google.com','http://www.yahoo.com','http://www.bing.com'
foreach ($site in $sites) {
Invoke-WebRequest $site
$site
}
From memory: You have to explicitly close the Response stream:
$req = [System.Net.HttpWebRequest]::Create($aRequestUrl);
$response = $null
try
{
$response = $req.GetResponse()
# do something with the response
}
finally
{
# Clear the response, otherwise the next HttpWebRequest may fail... (don't know why)
if ($response -ne $null) { $response.Close() }
}
I am using PowerShell 2.0 to write data to PowerShell's debug stream via the Write-Debug function. Now I want to read that stream from the same PowerShell script. I tried to redirect the debug stream with "2>&1", but this works only for the error stream.
Is there a way to read the PowerShell debug stream from a PowerShell script?
You can also experiment with your function that will be called instead of cmdlet Write-Debug. Here is a very quick implementation:
$global:__DebugInfo = new-object PSObject -prop #{
Enabled=$false
Messages=new-object System.Collections.ArrayList
}
function Write-Debug {
param([Parameter(Mandatory=$true)][string]$Message)
$d = Get-Command Write-Debug -CommandType cmdlet; & $d $Message;
if ($global:__DebugInfo.Enabled) {
$global:__DebugInfo.Messages.Add($Message) > $null
}
}
function Enable-Debug {
$global:DebugPreference = 'continue'
$global:__DebugInfo.Enabled = $true
}
function Disable-Debug {
$global:DebugPreference = 'silentlycontinue'
$global:__DebugInfo.Enabled = $false
}
# Test
Enable-Debug
Write-Debug 'this is test debug message'
Write-Debug 'switch off'
Disable-Debug
Write-Debug 'this message should not be included'
Write-Host "Debug messages:"
Write-Host ($__DebugInfo.Messages -join "`n")
It can be done but it is complex. See Oisin's writeup on script logging. In general, this issue of being able to redirect streams other than stdout and stderr has been logged as a suggestion on the connect site. You might want to vote on it.