locked
Problem with returning StringBuilder.ToString() RRS feed

  • Question

  • Hi!
    I`m using powershell v. 5.1.18362.628. 
    My code is below.

    function ShowError () {
        [System.Text.StringBuilder] $builder = [System.Text.StringBuilder]::new();
        $builder.Append("Error caught. ");
        $builder.Append("Exiting! ");
        $ans = $builder.ToString();
        return $ans;
    }
    function Main () {
        trap [System.Management.Automation.ActionPreferenceStopException]{
            throw $_.Exception;
            trap [System.Management.Automation.ItemNotFoundException] {
                $result = "";
                $result = ShowError;
                Write-Host $result -ForegroundColor Red;
                return;
            }
        }
    
        Get-Content -Path ./aaa.txt -ErrorAction Stop;
    }
    
    Main

    Unfortunately, $result variable is not "Error cought. Exiting! ".
    $result is an [Object[3]], where $result[0] is a system.text.stringbuilder object, $result[0] is a system.text.stringbuilder object, and $result[3] is a string object!
    In function ShowError I return System.String object, so why does a variable $result take such a value ?
    How can I solve this problem ?


    Saturday, February 15, 2020 1:52 PM

Answers

  • But I still don`t understand why it doesn`t work when I use functions...



    Because $result is getting all of the output of the ShowError() function. That includes the output of the $builder.append statements. Pipe that to out-null.

    function ShowError () {
        [System.Text.StringBuilder] $builder = [System.Text.StringBuilder]::new();
        $builder.Append("Error caught. ") | Out-Null
        $builder.Append("Exiting! ") | Out-Null
        $ans = $builder.ToString();
        return $ans;
    }


    For my own personal knowledge, can you explain your use of the trap commands? I do not understand why you are trapping an ActionPreferenceStopException and then immediately throwing the same exception! 

    Won't the nested trap command only be invoked if an error occurs within the processing of the first error that was trapped? Shouldn't it be coded like this?

    function ShowError () {
        [System.Text.StringBuilder] $builder = [System.Text.StringBuilder]::new();
        $builder.Append("Error caught. ") | Out-Null
        $builder.Append("Exiting! ") | Out-Null
        $builder.Append($_) | Out-Null 
        $ans = $builder.ToString();
        return $ans;
    }
    
    function Main () {
        trap [System.Management.Automation.ActionPreferenceStopException]{
                $result = ShowError;
                Write-Host $result -ForegroundColor Yellow;
                return
        }
         trap [System.Management.Automation.RuntimeException] {
                $result = ShowError;
                Write-Host $result -ForegroundColor Magenta;
                return;
         }
        if ($first1) {
            write-host "Getting content"
            Get-Content -Path ./aaa.txt -ErrorAction Stop;
            write-host "After getting content"
        }
        else {
            write-host "Bad math "
            1/$null 
            write-host "After bad math"
    
        }
    
    }
    
    $first1 = $true 
    Main
    $first1 = $false 
    Main

    Produces:




    • Edited by MotoX80 Saturday, February 15, 2020 4:46 PM
    • Marked as answer by Messing Jakob Saturday, February 15, 2020 6:02 PM
    Saturday, February 15, 2020 4:27 PM
  • It looks like you're trying to write Powershell code as if it were C#. :-)

    Using Try/Catch is easier to deal with than the Trap code you're trying to use. Check out the help for Trap to see how the scope of the exception is probably causing you your problem.

    The other thing to remember is that Powershell uses a pipeline, and your two $build.Append() statements both emit their results into the pipeline, as does your "return $ans". The result is that three objects are received by the caller of the function. Casting the $build.Append() as "[void]", or piping the result to "Out-Null" will prevent that and return just the result of the $build.ToString().

    This is all you need (and you really don't need to use the StringBuilder class -- just appending text to an existing string is okay, provided you're not doing it a million times, in which case you'd want to create your $builder object by providing an estimated final string length as an argument in the call to "new()").

    function ShowError {
        [System.Text.StringBuilder] $builder = [System.Text.StringBuilder]::new()
        [void]$builder.Append("Error caught. ")
        [void]$builder.Append("Exiting! ")
        $builder.ToString()
    }
    function Main {
        Try{
            Get-Content -Path ./aaa.txt -ErrorAction Stop
        }
        Catch [System.Management.Automation.ItemNotFoundException] {
            $result = ShowError
            Write-Host $result -ForegroundColor Red
        }    
    }
    
    Main


    --- Rich Matheisen MCSE&I, Exchange Ex-MVP (16 years)

    • Marked as answer by Messing Jakob Saturday, February 15, 2020 6:02 PM
    Saturday, February 15, 2020 5:05 PM

All replies

  • Hi!
    I`m using powershell v. 5.1.18362.628. 
    My code is below.

    function ShowError () {
        [System.Text.StringBuilder] $builder = [System.Text.StringBuilder]::new();
        $builder.Append("Error caught. ");
        $builder.Append("Exiting! ");
        $ans = $builder.ToString();
        return $ans;
    }
    function Main () {
        trap [System.Management.Automation.ActionPreferenceStopException]{
            throw $_.Exception;
            trap [System.Management.Automation.ItemNotFoundException] {
                $result = "";
                $result = ShowError;
                Write-Host $result -ForegroundColor Red;
                return;
            }
        }
    
        Get-Content -Path ./aaa.txt -ErrorAction Stop;
    }
    
    Main

    Unfortunately, $result variable is not "Error cought. Exiting! ".
    $result is an [Object[3]], where $result[0] is a system.text.stringbuilder object, $result[0] is a system.text.stringbuilder object, and $result[3] is a string object!
    In function ShowError I return System.String object, so why does a variable $result take such a value ?
    How can I solve this problem ?


    I solved the problem)

    trap [System.Management.Automation.ActionPreferenceStopException]{ throw $_.Exception; trap [System.Management.Automation.ItemNotFoundException] { $result = ""; $result = [Errors]::Main(); Write-Host $result -ForegroundColor Red; return; } } class MyClass { MyClass () { } static [void] Main() { Get-Content -Path ./aaa.txt -ErrorAction Stop; } } class Errors {
    Errors () {

    }

    static [system.string] Main() { [System.Text.StringBuilder] $builder = [System.Text.StringBuilder]::new(); $builder.Append("Error caught. "); $builder.Append("Exiting! "); $ans = $builder.ToString(); return $ans; } } [MyClass]::Main();

    But I still don`t understand why it doesn`t work when I use functions...



    Saturday, February 15, 2020 2:42 PM
  • But I still don`t understand why it doesn`t work when I use functions...



    Because $result is getting all of the output of the ShowError() function. That includes the output of the $builder.append statements. Pipe that to out-null.

    function ShowError () {
        [System.Text.StringBuilder] $builder = [System.Text.StringBuilder]::new();
        $builder.Append("Error caught. ") | Out-Null
        $builder.Append("Exiting! ") | Out-Null
        $ans = $builder.ToString();
        return $ans;
    }


    For my own personal knowledge, can you explain your use of the trap commands? I do not understand why you are trapping an ActionPreferenceStopException and then immediately throwing the same exception! 

    Won't the nested trap command only be invoked if an error occurs within the processing of the first error that was trapped? Shouldn't it be coded like this?

    function ShowError () {
        [System.Text.StringBuilder] $builder = [System.Text.StringBuilder]::new();
        $builder.Append("Error caught. ") | Out-Null
        $builder.Append("Exiting! ") | Out-Null
        $builder.Append($_) | Out-Null 
        $ans = $builder.ToString();
        return $ans;
    }
    
    function Main () {
        trap [System.Management.Automation.ActionPreferenceStopException]{
                $result = ShowError;
                Write-Host $result -ForegroundColor Yellow;
                return
        }
         trap [System.Management.Automation.RuntimeException] {
                $result = ShowError;
                Write-Host $result -ForegroundColor Magenta;
                return;
         }
        if ($first1) {
            write-host "Getting content"
            Get-Content -Path ./aaa.txt -ErrorAction Stop;
            write-host "After getting content"
        }
        else {
            write-host "Bad math "
            1/$null 
            write-host "After bad math"
    
        }
    
    }
    
    $first1 = $true 
    Main
    $first1 = $false 
    Main

    Produces:




    • Edited by MotoX80 Saturday, February 15, 2020 4:46 PM
    • Marked as answer by Messing Jakob Saturday, February 15, 2020 6:02 PM
    Saturday, February 15, 2020 4:27 PM
  • It looks like you're trying to write Powershell code as if it were C#. :-)

    Using Try/Catch is easier to deal with than the Trap code you're trying to use. Check out the help for Trap to see how the scope of the exception is probably causing you your problem.

    The other thing to remember is that Powershell uses a pipeline, and your two $build.Append() statements both emit their results into the pipeline, as does your "return $ans". The result is that three objects are received by the caller of the function. Casting the $build.Append() as "[void]", or piping the result to "Out-Null" will prevent that and return just the result of the $build.ToString().

    This is all you need (and you really don't need to use the StringBuilder class -- just appending text to an existing string is okay, provided you're not doing it a million times, in which case you'd want to create your $builder object by providing an estimated final string length as an argument in the call to "new()").

    function ShowError {
        [System.Text.StringBuilder] $builder = [System.Text.StringBuilder]::new()
        [void]$builder.Append("Error caught. ")
        [void]$builder.Append("Exiting! ")
        $builder.ToString()
    }
    function Main {
        Try{
            Get-Content -Path ./aaa.txt -ErrorAction Stop
        }
        Catch [System.Management.Automation.ItemNotFoundException] {
            $result = ShowError
            Write-Host $result -ForegroundColor Red
        }    
    }
    
    Main


    --- Rich Matheisen MCSE&I, Exchange Ex-MVP (16 years)

    • Marked as answer by Messing Jakob Saturday, February 15, 2020 6:02 PM
    Saturday, February 15, 2020 5:05 PM

  • For my own personal knowledge, can you explain your use of the trap commands? I do not understand why you are trapping an ActionPreferenceStopException and then immediately throwing the same exception! 


    First I catch System.Management.Automation.ActionPreferenceStopException, and, after that, I throw itemnotfoundexception.

    function Main () {
        trap {
            $_
        }
        trap [System.Management.Automation.ItemNotFoundException] {
            ShowError
        }
        Get-Content -Path ./aaa.txt -ErrorAction Stop
    }
    
    function ShowError() {
        [System.Text.StringBuilder] $builder = [System.Text.StringBuilder]::new()
        [void]$builder.Append("Error caught. ")
        [void]$builder.Append("Exiting! ")
        return $builder.ToString()
    }
    
    Main

    This code won`t catch ItemNotFoundException, But it will catch System.Management.Automation.ActionPreferenceStopException. 

    Saturday, February 15, 2020 6:00 PM
  • I understood the problem)
    Thank you for helping!)
    Saturday, February 15, 2020 6:02 PM
  • This code won`t catch ItemNotFoundException, But it will catch System.Management.Automation.ActionPreferenceStopException. 

    Sounds like Rich's suggestion of using try/catch would work better for you.  

    • Edited by MotoX80 Saturday, February 15, 2020 6:06 PM
    Saturday, February 15, 2020 6:05 PM
  • I do agree with you.

    It is much easier to use try - catch. 

    But I really liked to handle all the exceptions first, and then write the rest of the code)

    Saturday, February 15, 2020 6:23 PM
  • If you want to handle ALL exceptions you can remove the "[System.Management.Automation.ItemNotFoundException]" from the "Catch".

    If you want to continue handling the "ItemNotFound" exception in a specific way and handle all other exceptions in a generic way, you can add an unqualified "Catch" block below the block that's qualified with the "[System.Management.Automation.ItemNotFoundException]". Everything except the "ItemNotFound" exception will be caught by that unqualified Catch block.

        Try{
            Get-Content -Path ./aaa.txt -ErrorAction Stop
        }
        Catch [System.Management.Automation.ItemNotFoundException] {
            $result = ShowError
            Write-Host $result -ForegroundColor Red
        }
        Catch{
            <do something here with any exception except ItemNotFound>
        }	


    --- Rich Matheisen MCSE&I, Exchange Ex-MVP (16 years)

    Saturday, February 15, 2020 8:58 PM