1

I am writing a script that heavily utilizes an API. I have a large custom error handling function for the various error responses from Invoke-RestMethod calls that I would like to be able to unit test appropriately.

I saved some of these errors by using Export-CliXml, however I'm not actually able to rebuild the object back to it's original form. At some point when I'm expecting data I just get strings, and when I increase the -Depth parameter for Export-Clixml, Powershell takes a huge amount of time to write a gigantic .xml file.

For example I get an Http Response Error from Invoke-RestMethod, I check the status code and use that for some custom error handling in some of the scenarios. This is grabbed with $ErrorRecord.Exception.Response.StatusCode.Value__. However when I try to rebuild the CliXml object, everything held in the nested 'Response' object turns into just a string and I can't parse it in the same way.

So my questions are:

  1. Is there a good way to export a complicated, nested, error record object that doesn't take a huge amount of time to export and import?

  2. How is it that powershell gets the response and creates this error record in miliseconds, but my exporting and importing of the same object with it's nested properties takes an enormous amount of time?

Public testing API that will just return a 401 unauthorized error:

try {
    $global:response = Invoke-RestMethod -Uri "https://reqres.in/api/login" -Method 'POST'
}
catch {
    $global:err = $_
}
cassandrad
  • 3,412
  • 26
  • 50
Efie
  • 1,430
  • 2
  • 14
  • 34
  • Does this answer your question? [Is there a way to pass serializable objects to a PowerShell script with start-process?](https://stackoverflow.com/questions/34076478/is-there-a-way-to-pass-serializable-objects-to-a-powershell-script-with-start-pr), just search for `PowerShell serialize`: possible answer: `[System.Management.Automation.PSSerializer]::Serialize($object)`. – iRon Jun 24 '20 at 17:15
  • For some reason this doesn't complete the round trip either. $error.Exception.Response contents are still just a big string and I can't get $error.Exception.Response.StatusCode or $error.Exception.Response.StatusCode.value__ after serializing the error object and then deserializing it – Efie Jun 24 '20 at 17:43
  • Some objects can't be completely serialized because they have circular properties. Meaning that one of its properties refers back to a (grand)parent property. That is where e.g. the [`ConvertTo-Json -Depth` parameter](https://stackoverflow.com/a/53583678/1701026) comes from. – iRon Jun 24 '20 at 18:22
  • ConvertTo-Json almost works except that it will only return a PSCustomObject which cannot be casted into an errorrecord object type. The object is being saved somewhere in memory... isn't there some way to export that raw information to a file so that it can be brought back to life? – Efie Jun 24 '20 at 18:24
  • I didn't explicitly mention [ConvertTo-Expression](https://www.powershellgallery.com/packages/ConvertTo-Expression) as it is quite slow but you might want to try that. – iRon Jun 24 '20 at 19:25
  • It seems that this does not work on an HttpResonseException ErrorRecord object – Efie Jun 24 '20 at 19:48
  • As with `ConvertTo-Json` and explained, you need to limit the depth, e.g. `$err | ConvertTo-Expression -depth 3`. – iRon Jun 24 '20 at 19:54
  • It appears that the command in general doesn't work with ErrorRecord objects or at least in my situation: Line | 252 | ElseIf ($Object -is [Adsi]) { Stringify "'$($Object.A … | ~~~~~~ | Unable to find type [Adsi]. InvalidOperation: .../ConvertTo-Expression.ps1:275:9 Line | 275 | (Serialize $Object).TrimEnd() | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | You cannot call a method on a null-valued expression. – Efie Jun 24 '20 at 20:00
  • Edit: Didn't work with powershell core because of data type [asdi] I updated the script and it runs but I'm having trouble deserializing the expression. It also appears to only save as a pscustomobject so I don't think this will work. I may need to write a similar custom function. Thanks for your help, though – Efie Jun 24 '20 at 20:12

1 Answers1

1

ConvertTo-Json is a quick way to capture the exception and its nested properties. Specify -Depth to navigate through the object tree more than the default 2 layers:

$global:err = $_ | ConvertTo-Json -Depth 3

You can then deserialize it back to an object with ConvertFrom-Json and access the properties:

($Global:err | ConvertFrom-Json).ErrorDetails

If you include the code that's running slowly it will be easier to isolate its issue, but I expect that you might be encountering an issue with filesystem access rather than code. You can use Measure-Command to isolate issues with slow-running code.

EDIT: It looks like you were on the right track with ExportCli-Xml -- the built-in JSON serializer might not represent an object as well as CliXml. I missed the requirement that you needed to deserialize back to an Error object.

You might try skipping the Export and just use Management.Automation.PSSerializer. This code didn't raise any errors and was very quick:

$DeserializedError = [Management.Automation.PSSerializer]::DeSerialize([Management.Automation.PSSerializer]::Serialize($Error[0]))
Rich Moss
  • 2,195
  • 1
  • 13
  • 18
  • This seems to be the best answer so far but it still doesn't recreate the error object exactly. Before ConvertTo-Json: $err.Exception.Response.StatusCode is 'Bad Request' and $err.Exception.Response.StatusCode.value__ is '400'. After ConvertFrom-Json: $err.Exception.Response.StatusCode is '400' and .StatusCode.value__ is null – Efie Jun 24 '20 at 17:51
  • Also trying to cast back into a [System.Management.Automation.ErrorRecord] after Converting back from JSON throws an error – Efie Jun 24 '20 at 18:08