how to remove curly braces and get proper output in csv file - powershell

I m trying to get the list of vm from nutanix with name, ipaddress,
The output I am recieving includes ipaddress with curly braces which gives output as System.String[]
I have taken all the values in a array by using a for loop, than have exported the values to csv
Script which i have written is as follows-
foreach ($vmachine in $vm){
$obj = "" | Select "vmName", "ipAddresses", "description", "protectionDomainName", "powerState"
$obj.vmName = $vmachine.vmName
$obj.ipAddresses = $vmachine.ipAddresses
$obj.description = $vmachine.description
$obj.protectionDomainName = $vmachine.protectionDomainName
$obj.powerState = $vmachine.powerState
$outArrayVM += $obj
$obj =$null
}
$outArrayVM | Export-Csv d:\z.csv
Expected output should be some ipaddress like 10.x.x.x, but m getting #{ipAddresses=System.String[]}

This happens because $vmachine.ipAddresses is a string array object. You want a string representation of that with controlled formatting. There are many ways to accomplish this. Here is one that will join multiple IPs (if they exist) using a ;. If there is only one IP, it will appear with no semi-colon:
$obj.ipAddresses = $vmachine.ipAddresses -join ";"
Here's an example of your scenario:
$ip = #("10.1.23.45")
$ip.gettype()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
$obj.name = "test"
$obj.ip = $ip
$obj
name ip
---- --
test {10.1.23.45}
$obj | convertto-csv
#TYPE Selected.System.String
"name","ip"
"test","System.Object[]"
Converting the ip property of $obj to string forces PowerShell to interpret the property as a string rather than a collection. Thus, the braces notation ({}) goes away.
$obj.ip = $ip -join ";"
$obj | convertto-csv
#TYPE Selected.System.String
"name","ip"
"test","10.1.23.45"
Here are some other alternatives to set the ip property value as a string:
$obj.ip = -join $ip # No join character here. Works best with only one IP.
$obj.ip = $ip[0] # Accesses first element of array $ip, which will be a string. Only works with one IP.
$obj.ip = [string]$ip # Uses string type accelerator to cast $ip as string. This will join multiple IPs with a space between each IP.
Explanation:
When a ConvertTo-Csv or Export-Csv is run, the input object property is converted using the ToString() method. If the reference type of that object property (System.Array in this case) does not have an override for the ToString() method, then that method will return the fully qualified type name of the property. In this instance, that FQTN is System.Object[]. This is predictable with a little digging.
Testing with [Int32], you would expect the string conversion to provide a string representation of the integer data because it does have an override:
$int = 1
$int.gettype().fullname
System.Int32
($int | Get-Member).where{$_.Name -eq "ToString"}
TypeName: System.Int32
Name MemberType Definition
---- ---------- ----------
ToString Method string ToString(), string ToString(string format), string ToString(System.IFormatProvider provid...
$int.ToString()
1
$int.ToString().gettype().fullname
System.String
Testing with [Array], you would not expect the string conversion to provide a string representation of the array data because it does not have an override:
$arr = [array]1
$arr.gettype().fullname
System.Object[]
([System.Object[]] | Get-Member -Static).where{$_.name -eq "ToString"}
$arr.toString()
System.Object[]
See Export-Csv and Object.ToString Method for supplemental explanations and examples.

Related

Unable to export system.object in a readable form using PowerShell

I've looked through other answers and I am still struggling. I have some data that comes from a RESTAPI and I get that data using Invoke-RestMethod so it is already converted from the returned JSON
$scans = Invoke-RestMethod -URI "https://<url>/api/v1/<api key>/scans"
I get back an object with two fields
Message:
Data:
$Scans.Data contains a hash table converted from the JSON output with each entry in the table having the following key:value pairs
ScanID
Username
Targets
Name
This is the output of $scans | Get-Member
$scans | gm
TypeName: System.Management.Automation.PSCustomObject
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
name NoteProperty string name=<redacted>
scan_id NoteProperty string scan_id=<redacted>
targets NoteProperty Object[] targets=System.Object[]
user_name NoteProperty string user_name=<redacted>
I need to export all data into a CSV, but $scan.data.targets only shows as System.Object[]
I can't use -expandproperty because I am selecting multiple fields.
How do I convert $scans.data.targets into a readable form for the export, whilst keeping it linked with the other fields for that entry?
something equivalent to:
$scans.data | export-CSV <filepath>
but exporting in a format that is readable when I open the CSV
As commented, you could opt for a combined field of targets in the output:
# demo
$scan = [PsCustomObject]#{data = [PsCustomObject]#{name = 'blah'; scan_id = 123; targets = 'abc','def','ghi'; user_name = 'ItIsMe'}}
$scan.data | Select-Object *, #{Name = 'targets'; Expression = {$_.targets -join '; '}} -ExcludeProperty targets |
Export-Csv -Path 'D:\Test\blah.csv' -NoTypeInformation
which (when displayed in the console) looks like this:
name scan_id user_name targets
---- ------- --------- -------
blah 123 ItIsMe abc; def; ghi
Or create output where every target gets its own row in the CSV:
$result = foreach ($item in $scan.data) {
foreach ($target in $item.targets) {
[PsCustomObject]#{
Name = $item.name
ScanID = $item.scan_id
Target = $target
UserName = $item.user_name
}
}
}
# output on screen
$result
# output to CSV file
$result | Export-Csv -Path 'D:\Test\blah.csv' -NoTypeInformation
which gives you this:
Name ScanID Target UserName
---- ------ ------ --------
blah 123 abc ItIsMe
blah 123 def ItIsMe
blah 123 ghi ItIsMe

How to prepend a "string" to a "string[]" variable?

Let's suppose I have a PowerShell function that returns a [string[]]:
function do_something() {
[string[]]$str_array = #("one", "two", "three")
return $str_array
}
$x = do_something()
# doesn't work
$y = #("something", $x)
Now I want to prepend a string to the front of the [string[]] list.
How would I go about doing that?
There are several ways to join two arrays. Regarding your question in the above comment:
That's interesting, I wasn't aware that a "," is different from a ";" when forming a list...
In PowerShell, the , is an own operator, where its behavior differs if used in a unary or binary statement.
From about_operators:
As a binary operator, the comma creates an array. As a unary operator, the comma creates an array with one member. Place the comma before the member.
In the code #("something", $x) you're using the operator in binary form. So a new array with two entries is created. The first entry is the string "something", the second entry contains an array of strings. Thats because PowerShell interprets it as: "Ok, I shall create a new array with two entries (="something", and a reference pointing to an array).
We can check that with the help or Get-Member:
C:\> $y | Get-Member
TypeName: System.String
Name MemberType Definition
---- ---------- ----------
Clone Method System.Object Clone(), System.Object ICloneable.Clone()
...
TypeName: System.Object[]
Name MemberType Definition
---- ---------- ----------
Add Method int IList.Add(System.Object value)
...
As can be seen, Get-Member shows use two TypeNames. The first is a string, the second is an array.
When changing the code to #("something"; $x) no ,-operator is used anymore. Instead, the code is now split up in about three "command", since the command-separator (=;) is used.
Cmd1: "something"
Cmd2: $x -> which dumps the array content
Cmd3: #() -> guarantees that the expressions inside the brackets are returned as an array.
You can also remove the command seperator and define $y over multiple lines:
PS C:\>$y = #("something"
>>> $x
>>> )
The be less ambiguous I'd recommend to merge two arrays in the following way:
PS C:\> $a = 1
PS C:\> $b = #(2,3,4)
PS C:\> $c = #()
PS C:\> $c += $a
PS C:\> $c += $b
PS C:\> $c
1
2
3
4
PS C:\> $c | gm
TypeName: System.Int32
Name MemberType Definition
...
Of course, it's "more" code, but its less ambiguous.
Hope that helps.

Import-Csv data parse to array

I have a CSV file which contains multiline in some cells. I will use this data to compare the value got it from powershell.
This returns differences between the object, however the value is the same.
Expected Results should return nothing because both values are the same.
CSV content:
Value
System\CurrentControlSet\Control\ProductOptions
System\CurrentControlSet\Control\Server Applications
Software\Microsoft\Windows NT\CurrentVersion
Code:
PS> $data = Import-Csv .\tr.csv
PS> $data.Value
System\CurrentControlSet\Control\ProductOptions
System\CurrentControlSet\Control\Server Applications
Software\Microsoft\Windows NT\CurrentVersion
PS> $regval = ((Get-ItemProperty HKLM:\SYSTEM\CurrentControlSet\Control\SecurePipeServers\winreg\AllowedExactPaths).machine | Out-String).Trim()
PS> $regval
System\CurrentControlSet\Control\ProductOptions
System\CurrentControlSet\Control\Server Applications
Software\Microsoft\Windows NT\CurrentVersion
PS> Compare-Object $data.Value $regval
System\CurrentControlSet\Control\ProductOptions... =>
System\CurrentControlSet\Control\ProductOptions... <=
PS> $Tostring = ($data.Value | out-string).Trim()
PS> Compare-Object $Tostring $regval
InputObject SideIndicator
----------- -------------
System\CurrentControlSet\Control\ProductOptions... =>
System\CurrentControlSet\Control\ProductOptions... <=
PS> $Tostring.Length
145
PS> $regval.Length
147
This post no longer answers the OP's question directly but provides background information that is helpful for similar situations. This particular issue is solved by handling CR and LF characters before comparing the data. See Marked Answer for details.
Since $data in this case is an object with a property called value that holds your data, you need to compare what is stored in the value property to your $regval:
Compare-Object $data.value $regval
$regval is an array of strings before you pipe it to Out-String. After the pipe, it then becomes a string object. See below for type information before piping to Out-String.
$regval.gettype().fullname
System.String[]
$data is an array of objects (PSCustomObjects), which each have a property called Value that needs to be referenced directly if you want its data:
$data.gettype().fullname
System.Object[]
$data | Get-Member
TypeName: System.Management.Automation.PSCustomObject
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
Value NoteProperty string Value=System\CurrentControlSet\Control\ProductOptions
($regval | Get-member).where({$_.MemberType -eq "Property"})
TypeName: System.String
Name MemberType Definition
---- ---------- ----------
Length Property int Length {get;}
In order to compare the data of two objects using Compare-Object, best results seem to come when the objects are collections of the same type. PowerShell will automatically do conversions in the background in some cases like Compare-Object "1" 1. Maybe that has something to do with value types as I am not entirely sure. I would do the comparison before converting any of your data to different types. Then if you reference the Value property of $data, this condition becomes true:
$data.value | Get-member -type Property
TypeName: System.String
Name MemberType Definition
---- ---------- ----------
Length Property int Length {get;}
You can reference MKlement0's Explanation for more information about how PowerShell handles the array type.
The likeliest explanation is that:
the multi-line value from your CSV (obtained from a single field) contains LF-only (Unix-style) newlines,
whereas the string derived form the registry values has CRLF (Windows-style) newlines, due to applying Out-String to an array of strings.
The most direct fix is to remove the CR chars. from $regval (you can use "`r" in PowerShell to generate a CR char):
# ...
# Remove all CRs from $regval.
# Note that not providing a replacement string (missing 2nd RHS operand)
# default to the empty string, which effectively *removes* what was matched.
$regval = $regval -replace "`r"
# Should now work as expected.
Compare-Object $data.Value $regval
That said:
Since you're comparing just two objects that are strings, you can avoid the overhead of Compare-Object and simply use -eq:
$data.Value -eq $regVal
Alternatively, you can split the multi-line values into arrays of lines and compare them individually; note that if you use regex "`r?`n" or ('\r?\n') to match newlines to split by - which matches both LF-only and CRLF newlines - you needn't remove CR chars. beforehand or even apply Out-String to the array output from the Get-ItemProperty HKLM:\... call to begin with; however, with the variable values from your question, you'd use:
# Newline-style-agnostic
Compare-Object ($data.Value -split "`r?`n") ($regval -split "`r?`n")
# Or, knowing that $data.Value has LF, and $regval CRLF
Compare-Object ($data.Value -split "`n") ($regval -split "`r`n")
# Or, by using the [string[]] array of registry values directly:
$regvals = (Get-ItemProperty HKLM:\SYSTEM\CurrentControlSet\Control\SecurePipeServers\winreg\AllowedExactPaths).machine
Compare-Object ($data.Value -split "`n") $regvals
As for what you tried:
$Tostring = ($data.Value | out-string).Trim()
If $data.Value is a single string that doesn't have a trailing newline - whether or not it has embedded newlines - the above is an effective no-op:
An input object that is already a string is passed through as-is by Out-String.
While Out-String does append a trailing CRLF newline (on Windows), the subsequent .Trim() call removes it again.

How use "Where-Object" condition with '[PScustomobject]'?

I have some code:
$output = [PSCustomObject]#{
Name = $ws.UsedRange.Columns.Item(1).Value2
Department = $ws.UsedRange.Columns.Item(3).Value2
}
$output | GM
TypeName: System.Management.Automation.PSCustomObject
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
Department NoteProperty System.Object[,] Department=System.Object[,]
Name NoteProperty System.Object[,] Name=System.Object[,]
I need to sort and filter my $output, but I can't. Nothing happens. Probably doing something wrong.
PS> $output
Name Department
---- ----------
{Numbers, 1,2,3,4,5,6,7...} {Sales,IT,Accounting,Developers...}
And my condition:
PS> $output | Sort-Object Department -Descending | Where-Object {$_.Department -eq "Sales"}
Name Department
---- ----------
{Numbers, 1,2,3,4,5,6,7...} {Sales,IT,Accounting,Developers...}
You created a single object with 2 properties, each of which contains all values of its associated column. Since Sort-Object and Where-Object sort and filter lists of objects by their properties there's nothing for these cmdlets to do here.
What you actually want to do is create one object per row.
$output = foreach ($row in $ws.UsedRange.Rows) {
[PSCustomObject]#{
Name = $row.Columns.Item(1).Value2
Department = $row.Columns.Item(3).Value2
}
}
Untested, since I don't have MS Office at hand here.

Confused Powershell returned array type

The following shows that the returned type are different depends on how many rows returned. Why it's designed this way? It's very easy to make assumption it always returns an array and write the code $a.Length $a | % { ....} which will raise error when it returns only one row, unless the it's written as $a = #(invoke-....), which is easy to forget.
$a=Invoke-Sqlcmd -ServerInstance server "select 1 a"
$b=Invoke-Sqlcmd -ServerInstance server "select 1 a union all select 2"
$a.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False DataRow System.Object
And the following statement returns an array of object (BTW, why not an array of DataRow?)
$b.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
However, gm returns the same type for both variables. Why it's designed this way which can be very confused.
Question:
What's the point that the array is removed when only one item is returned?
Why gm get item type of an array? How to gm of an array?
Why getType() cannot return the data type of the item when it returns an array type?
?
PS SQLSERVER:\> $a|gm
TypeName: System.Data.DataRow
Name MemberType Definition
---- ---------- ----------
AcceptChanges Method void AcceptChanges()
......
ToString Method string ToString()
Item ParameterizedProperty System.Object Item(int columnIndex) {get;set;}, System.Object Item(string co...
a Property int a {get;set;}
PS SQLSERVER:\> $b|gm
TypeName: System.Data.DataRow
Name MemberType Definition
---- ---------- ----------
AcceptChanges Method void AcceptChanges()
......
ToString Method string ToString()
Item ParameterizedProperty System.Object Item(int columnIndex) {get;set;}, System.Object Item(string co...
a Property int a {get;set;}
Most of the time in PowerShell, functions/cmdlets that return objects, simply return the object. If more than 1 object is to be returned, the function just keeps returning objects until it's done. PowerShell handles all of the returned objects and gives you an array.
gm is an alias for Get-Member. When you call it by piping to it $a | gm, you are invoking it as a pipeline. In this case, each object in $a is individually passed to Get-Member, so it returns the type of the individual object(s). If they are all the same, then it will only display it once, but it's actually checking all of them. You can prevent this by calling Get-Member -InputObject $a which should show you the array type if it is an array.
Similar to the above, .GetType() gets the type of whatever object it's invoked on, so if it's an array, then it returns that; it's not looking at the individual elements.
I also want to point out that % (ForEach-Object) and foreach() work fine when not used on an array: "hello" | % { $_ }, as does foreach($msg in "hello") { $msg }.
To address the issue of $a.Length not working when the return value is a single object, it depends on your powershell version.
In version 2, you will see the behavior you are seeing: you'll have to wrap it in an array first, or test for an array with something like:
if ($a -is [Array]) {
$itemCount = $a.Length
} else {
$itemCount = 1
}
But, in powershell 3+, you can do $a.Length even if $a is just some object and not an array. It will return 1. Length may not show up in autocomplete or in intellisense, but it will work.