Formatting the output of a variable

I have a script which runs a PowerCLI command like this:

Get-IScsiHbaTarget -IScsiHba $hba | Sort Address

The output is like this:

==========================================
iSCSI Targets on MyVMHost.mydomain.com

Address Port Type


172.20.20.80 3260 Static
172.20.20.80 3260 Send
172.20.20.81 3260 Static

I then changed the line so that the command was assigned to a variable, so that I could test the validity of the variable before displaying it in the console window:

$x=Get-IScsiHbaTarget -IScsiHba $hba | Sort Address

When I run the code in the PowerShell ISE, the output is correctly displayed on multiple lines. However when I run the code in a PowerShell console, it displays like this:

==========================================
iSCSI Targets on MyVMHost.mydomain.com

Static-172.20.20.80:3260 Send-172.20.20.80:3260 Static-172.20.20.81:3260

Why is this happening?

Name Value


PSVersion 5.1.14409.1018
PSEdition Desktop
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0…}
BuildVersion 10.0.14409.1018
CLRVersion 4.0.30319.42000
WSManStackVersion 3.0
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1

@JDMils Welcome to PowerShell.org forums.

You are probably printing the variable wrapped in double quotes.

Wrapping a variable in double quotes will do a string expansion.

The variable $X is assigned the value of the vSphere PowerCLI function mentioned, so there’s no quoting going on.

Well to be fair you don’t show how you retrieve the variable value. You could be sticking it in a form for all we know. It was a good guess as it does look like it was forced into a single string. Please share how you are outputting the stored variable.

1 Like

One more thing I noticed was this:
When the script loops the first time, the output is:

==========================================
iSCSI Targets on MyHost01.mydomain
==========================================

Address              Port  Type  
-------              ----  ----  
172.20.20.80        3260  Static
172.20.20.80        3260  Send  
172.20.20.81        3260  Static

I paused the script and checked the value of $x from the ISE command line:

[DBG]: PS C:\Users\Julian\Documents\WindowsPowerShell\Scripts> $x

Address              Port  Type  
-------              ----  ----  
172.20.20.80        3260  Static
172.20.20.80        3260  Send  
172.20.20.81        3260  Static

Exactly what I expected: What is output from the script is mirrored from the variable value.

The script then runs the next iteration of the loop, reporting on the next vSphere host, and this is the output of the script:

==========================================
iSCSI Targets on MyHost02.mydomain
==========================================
172.20.80.80        3260  Static
172.20.80.81        3260  Static

I paused the script again at the same point (using a breakpoint), and checked the value of the variable $x from the ISE command line:

[DBG]: PS C:\Users\Julian\Documents\WindowsPowerShell\Scripts> $x
 
Address              Port  Type  
-------              ----  ----  
172.20.80.80        3260  Static
172.20.80.81        3260  Static

As you can see, the output from the script does NOT match the variable value???

The variable is an object, obviously, so what’s going on here?
I don’t understand why the script runs and each iteration of the loop produces a different output.

Thanks.

This is the original script:

$esxHosts = Get-VMhost
foreach($esx in $esxhosts){
	$hba = $esx | Get-VMHostHba -Type iScsi 
	if ($hba)
		{
	    Write-Host "=========================================="
	    Write-Host "iSCSI Targets on $esx"
	    Write-Host "=========================================="
	    # Get-IScsiHbaTarget -IScsiHba $hba -Type Send | Sort Address
		$x=Get-IScsiHbaTarget -IScsiHba $hba | Sort Address
		Write-Host $x
	    Write-Host " "
		}
}

When I run the script from the ISE, the output is this:

==========================================
iSCSI Targets on MyHost01.mydomain
==========================================
Static-172.20.20.80:3260 Send-172.20.20.80:3260 Static-172.20.20.81:3260

If I change the line:

Write-Host $x

To:

Write-Output $x

The output is now this:

==========================================
iSCSI Targets on MyHost01.mydomain
==========================================

Address              Port  Type  
-------              ----  ----  
172.20.20.80        3260  Static
172.20.20.80        3260  Send  
172.20.20.81        3260  Static
 
==========================================
iSCSI Targets on MyHost02.mydomain
==========================================
172.20.20.80        3260  Static
172.20.20.81        3260  Static

So Write-Output seems to output the linefeeds and carriage returns whereby Write-Host does not?

With a bit of experimenting, I think I may have found the solution. I changed the following lines:

$x=Get-IScsiHbaTarget -IScsiHba $hba | Sort Address
Write-Output $x -ForegroundColor Green

As above, the output was in Green but not formatted correctly. The new code looks like this:

$x=Get-IScsiHbaTarget -IScsiHba $hba | Sort Address
$Output = Out-String -InputObject $x
Write-Host $Output -ForegroundColor Green

Looks like Output-Object correctly outputs the object $x as its value is formatted, and by assigning it to a string variable, I can ouput the variable, using Write-Host, in colour!

Note:
$x is type System.Array
$Output is type System.String

Write-Host always stringifies the value. Just output $x will output the object, the same output as the command

$x

What method of outputting $x would you use? You can’t just put $x on a line by itself within the script as that is not recognised as the name of any cmdlets. It has to be output using a PowerShell method.

That is incorrect. But the question is too vague. If I really just wanted it to output it’s default just like the first command, then yes, just drop $x and let powershells implicit output handle it or don’t store it in a variable at all.

Run each of these independently. Note that even though $x isn’t surrounded by quotes in the 4th example, Write-Host always converts the input to string.

# The following 2 examples will have the same "object" output dictated by its own default formatting rules
foreach($esx in $esxhosts){
	$hba = $esx | Get-VMHostHba -Type iScsi 
	if ($hba){
        Write-Host "=========================================="
        Write-Host "iSCSI Targets on $esx"
        Write-Host "=========================================="
        # Get-IScsiHbaTarget -IScsiHba $hba -Type Send | Sort Address
        Get-IScsiHbaTarget -IScsiHba $hba | Sort Address
    }
}

foreach($esx in $esxhosts){
	$hba = $esx | Get-VMHostHba -Type iScsi 
	if ($hba){
        Write-Host "=========================================="
        Write-Host "iSCSI Targets on $esx"
        Write-Host "=========================================="
        # Get-IScsiHbaTarget -IScsiHba $hba -Type Send | Sort Address
        $x = Get-IScsiHbaTarget -IScsiHba $hba | Sort Address
        $x
    }
}

# these two will be the same incorrectly stringigified versions
foreach($esx in $esxhosts){
	$hba = $esx | Get-VMHostHba -Type iScsi 
	if ($hba){
        Write-Host "=========================================="
        Write-Host "iSCSI Targets on $esx"
        Write-Host "=========================================="
        # Get-IScsiHbaTarget -IScsiHba $hba -Type Send | Sort Address
        $x = Get-IScsiHbaTarget -IScsiHba $hba | Sort Address
        "$x"
    }
}

foreach($esx in $esxhosts){
	$hba = $esx | Get-VMHostHba -Type iScsi 
	if ($hba){
        Write-Host "=========================================="
        Write-Host "iSCSI Targets on $esx"
        Write-Host "=========================================="
        # Get-IScsiHbaTarget -IScsiHba $hba -Type Send | Sort Address
        $x = Get-IScsiHbaTarget -IScsiHba $hba | Sort Address
        Write-Host $x
    }
}
1 Like

What you’re seeing in the first example is the default member (property) set. It’s output nice for your eyes by the powershell formatting system based on the cmdlets own format specification. Now a lot of cmdlets will make the ToString() method actually output the same or something just as nice for human consumption. However some, like this IscsiHBA cmdlet, doesn’t offer anything special for ToString() so powershell gets garbage when it asks it to “stringify” itself.

Now for what you see on the screen, if you want to capture the text exactly like that and don’t care about object, just Out-String it. Check these examples.

# Get-Member on the default, easy on the eyes output. 
foreach($esx in $esxhosts | Select-Object -First 1){
	$hba = $esx | Get-VMHostHba -Type iScsi 
	if ($hba){
        Write-Host "=========================================="
        Write-Host "iSCSI Targets on $esx"
        Write-Host "=========================================="
        # Get-IScsiHbaTarget -IScsiHba $hba -Type Send | Sort Address
        $x = Get-IScsiHbaTarget -IScsiHba $hba | Sort Address
        $x # implicit output so you can see it's formatted the way you like
        $x | Get-Member
    }
}

# Now we'll show the same output but this time it's been turned into pretty, but hard to work with down the pipeline, string
foreach($esx in $esxhosts | Select-Object -First 1){
	$hba = $esx | Get-VMHostHba -Type iScsi 
	if ($hba){
        Write-Host "=========================================="
        Write-Host "iSCSI Targets on $esx"
        Write-Host "=========================================="
        # Get-IScsiHbaTarget -IScsiHba $hba -Type Send | Sort Address
        $x = Get-IScsiHbaTarget -IScsiHba $hba | Sort Address
        $x | Out-String 
        $x | Out-String | Get-Member
    }
}

So even though both show the same output on the screen, one is still an object and the other is not. Now if someone made the cmdlet’s ToString() method do that, it’d be a lot easier to use in double quotes or Write-Host.

So it really comes down to what you are trying to do with the output. Are you wanting to both show it to use and capture the object? Perhaps just do both, because the -Host cmdlets aren’t sent on stdout so it won’t be captured (without trying hard to do so) while the object does, try it yourself!

# Pretty output for the eys and only the objects are collected in $result. 
$result = foreach($esx in $esxhosts){
	$hba = $esx | Get-VMHostHba -Type iScsi 
	if ($hba){
        Write-Host "==========================================" -ForegroundColor DarkCyan
        Write-Host "iSCSI Targets on $esx" -ForegroundColor Gray
        Write-Host "==========================================" -ForegroundColor DarkCyan
        # Get-IScsiHbaTarget -IScsiHba $hba -Type Send | Sort Address
        $x = Get-IScsiHbaTarget -IScsiHba $hba | Sort Address
        Write-Host ($x | Out-String) -ForegroundColor Cyan
        $x
    }
}
2 Likes

Thanks KrzyDoug, that’s a gr8 explanation!

As you can tell from the code, the result of ‘Get-IScsiHbaTarget’ is simply being used to show the user the result, in this case anyway.

1 Like