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()
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.
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
I'm using powershell to check several different sites for data. If the site has no data for today, it will throw a NullReferenceException. I'd like for the script to output a message that there is no data, then continue on to the other sites without halting.
In Java, I can simply just try/catch/finally, but Powershell isn't acting as nicely.
try {
$webRequest = Invoke-WebRequest -URI "http://###.##.###.##:####/abcd.aspx?"
} catch [System.NullReferenceException]{
Write-Host "There is no data"
}
The full error displays in console, and the Write-Host never actually appears.
try {
$webRequest = Invoke-WebRequest -URI "http://host/link" -erroraction stop
}
catch [System.NullReferenceException]{
Write-Host "There is no data"
}
Powershell distinguishes between terminating and non terminating errors, for catch to work, you need the error to be terminating. https://blogs.technet.microsoft.com/heyscriptingguy/2015/09/16/understanding-non-terminating-errors-in-powershell/
UPD: to get the type of exception, after you get the error just do:
$Error[0].Exception.GetType().FullName
and you use that to catch that specific error after
to continue on specific error with invoke-webrequest you can do something like this:
try { Invoke-WebRequest "url" }
catch { $req = $_.Exception.Response.StatusCode.Value__}
if ($req -neq 404) { do stuff }
That's probably because the exception may not be a Null Reference exception. I Initially thought it to be a Non-Terminating error, but invoke-webrequest throws a terminating error.
In this case, you may simply try (without catching a specific exception type)
--Edited Per OP Comments--
try
{
Invoke-WebRequest -URI "http://doc/abcd.aspx?" -ErrorAction Stop
}
catch
{
if($_.Exception.GetType().FullName -eq "YouranticipatedException")
{
Write-Host ("Exception occured in Invoke-WebRequest.")
# You can also get the response code thru "$_.Exception.Response.StatusCode.Value__" if there is response to your webrequest
}
I have weird problem, when im using try/catch method for some cmdlets its working for some not.
Can you advice on that?
This one is working fine:
try
{
$LookingForRemoteMailboxOnPrem = Get-RemoteMailbox $info -ErrorAction Stop | select -ExpandProperty UserPrincipalName
}
catch
{
string]$t = $Error[0]
}
But this one is not:
try
{
$EnableRemoteMailbox = Enable-RemoteMailbox $info -RemoteRoutingAddress $remote -PrimarySmtpAddress $info2 -ErrorAction Stop
}
catch
{
[string]$t = $Error[0]
}
Not saving error to $t variable
The $ErrorActionPreference is set to Continue by default. This means if PowerShell can "recover" from an error it won't throw an exception. You can use the -ErrorAction parameter to change the behaviour at every cmdlet.
This link gives a good example:
Try {dir c:\missingFolder}
Catch [System.Exception] {"Caught the exception"}
Finally {$error.Clear() ; "errors cleared"}
The string "Caught the exception does not occur in PowerShell windows. If you set the -ErrorAction to Stop an exception is raised.
Details are described here.
I noticed that if applying a configuration through Start-DscConfiguration fails, it writes to the error stream but doesn't
throw an Exception? That is, if I do the following:
try{
Start-DscConfiguration -Path ".\MyConfig" -Wait -Verbose
}catch{
#...
}
...it never ends up in the catch handler. I suspect this may have something to do with the fact that without the "-Wait",
Start-DscConfiguration starts an async job for this, and async commands probably don't throw exceptions, but in a synchronous
scenario, I would very much like to know if my configuration could be applied.
What is the proper way to determine if Start-DscConfiguration has completed succesfully?
The only way I know is to check the global "$error" variable and compare the number of error records before and after your call to Start-DscConfiguration. If there's more afterwards then something must have gone wrong during the call, so throw your own exception:
Configuration TestErrorHandling {
Node "localhost" {
Script ErroringResource {
GetScript = { return $null; }
TestScript = { return $false; }
SetScript = { throw new-object System.InvalidOperationException; }
}
}
}
$errorCount = $error.Count;
write-host "starting dsc configuration"
$mof = TestErrorHandling;
Start-DscConfiguration TestErrorHandling –Wait –Verbose;
write-host "dsc configuration finished"
if( $error.Count -gt $errorCount )
{
$dscErrors = $error[$errorCount..($error.Count - 1)];
write-host "the following errors occurred during dsc configuration";
write-host ($dscErrors | fl * | out-string);
throw $dscErrors[-1];
}
There's another way to make it cause an exception. Try saving it into the ErrorVariable like this :
try
{
Start-DscConfiguration -Path ".\MyConfig" -Wait -Verbose -ErrorVariable ev
}
catch
{
$myException = $_
}
Weirdly so, this throws the exception when there's an error (which is what you wanted). You can get the value of your exception in the $myexception variable, and also could get just a one liner description of your error using $ev
PS: Note that while mentioning ev in the errorVariable parameter, you do it without the '$' symbol - since you're only specifying the variable 'name'.
Start-DscConfiguration when used without -Wait will create a job object - with one child job for every computername. PowerShell job objects have an Error stream which contains all the errors. You can check this stream as well
$job = Start-DscConfiguration -Force -Verbose -Path C:\Temp\Demo\ -ComputerName localhost
Receive-Job $job -Wait
'Errors in job = ' + ($job.childjobs[0].Error.Count)