How to use PS calculated properties to collate data into a cell? - powershell

I'm stumped on this one. I have a PS custom object with strings only and I want to build a report where I'm outputting strings of data into a new pipeline output object.
$myObjectTable |
Select-Object #{
n = "OldData";
e = {
$_ | Select-Object name, *_old | Format-List | Out-String
}
},
#{
n = "NewData";
e = {
$_ | Select-Object name, *_new | Format-List | Out-String
}
}
Running this produces blank output.
I tried running the code above with just the $_ object in the expressions, but I only got ... as the output. Wrapping the expressions in parenthesis did not change the output.

The ... as property value means that the value is either a multi-line string or it just didn't fit in a tabular format. See Using Format commands to change output view more details.
You can fix those empty lines added by Out-String using Trim. Then if you want to properly display this object having multi-line property values, Format-Table -Wrap will be needed.
Here is a little example:
[pscustomobject]#{
Name = 'foo'
Something_Old = 123
Something_New = 456
} | ForEach-Object {
[pscustomobject]#{
OldData = $_ | Format-List name, *_old | Out-String | ForEach-Object Trim
NewData = $_ | Format-List name, *_new | Out-String | ForEach-Object Trim
}
} | Format-Table -Wrap
Resulting object would become:
OldData NewData
------- -------
Name : foo Name : foo
Something_Old : 123 Something_New : 456

Related

Formatting the result set in PowerShell

Below is my query to get the space details:
$myarray += $finaldata
foreach ($dr in $Drive)
{
Switch ($dr)
{
E { $myarray | select Servername,EDriveFreeSpace | ft}
H { $myarray | select Servername,HDriveFreeSpace | ft}
I { $myarray | select Servername,IDriveFreeSpace | ft}
O { $myarray | select Servername,ODriveFreeSpace | ft}
}
}
}
if(!$Drive)
{
$myarray |ft
}
}
Output:
Servername EDriveFreeSpace
CVRFDGXXXX1 734
CVRFDGXXXX2 986
Servername ODriveFreeSpace
CVRFDGXXXX1 547
CVRFDGXXXX2 718
Can I get the result set in below format:
Servername EDriveFressSpace ODriveFressSpace
CVRFDGXXXX1 734 547
CVRFDGXXXX2 986 718
Any changes I need to do to get output in the above format, Any suggestions would be appreciated.
Thanks
If I understand correctly, you're looking for this (using the full name of the cmdlet that alias ft refers to, Format-Table):
$myArray | Format-Table -Property ServerName, [EHIO]FreeDriveFreeSpace
This assumes that at least the first object in $myArray has all the properties whose name the wildcard expression [EHIO]FreeDriveFreeSpace matches.
If that condition isn't met, you can use an aux. Select-Object call to construct custom objects that do have all these properties:
$myArray |
Select-Object -Property ServerName, EFreeDriveFreeSpace, HFreeDriveFreeSpace, IFreeDriveFreeSpace, OFreeDriveFreeSpace |
Format-Table

Running Powershell command Out-File in Azure Pipeline cuts lines

As part of a CI pipeline, I need to generate a list of all the files in the repository, including certain properties of them, and to Out-File it into a file. The command I use:
Get-ChildItem $Path -File -Recurse | Select-Object -Property LastWriteTime, #
{
label = "Size(KB)"
expr = { [string]::Format("{0:0.00}", $_.Length/1KB) }
}, FullName, <some_other_property> | Out-File $OutputFile
My problem is, that running this script from the command line gives the desired result.
However, running this during a Azure Pipeline build does 2 bad things:
cuts the lines in the FullName column when they are long:
LastWriteTime Size(KB) Name
------------ ------- ----
<some_date> <some size> ASomeWhatLong...
Doesn't display the rest of the properties such as <some_other_property>
If I turn FullName into Name it all goes OK, but I really need the FullName property.
Because I'm working in a Air-gapped environment I can't copy all the outputs and everything.
I've tried using the -Width flag for Out-File with no success.
I believe what's happening under the hood, ps uses the ToString() method of the object created which outputs it as the Format-Table cmdlet does. You get truncated properties because of the Window's size. To look at it you could use:
(Get-Host).ui.RawUI.WindowSize
Probably this is too small.
What I would suggest is the following:
Pipe the object into Format-Table
Get-ChildItem $Path -File -Recurse | Select-Object -Property LastWriteTime, #
{
label = "Size(KB)"
expr = { [string]::Format("{0:0.00}", $_.Length/1KB) }
}, FullName, <some_other_property> | Format-Table | Out-String | Out-File $OutputFile
This probably won't work as it is, but you could play with the Format-Table's properties like: -Wrap. By default it will allocate enough space for the first properties, and the last one it would try to 'fit' it, which might look as:
LastWriteTime Size(KB) Name
------------ ------- ----
<some_date> <some size> ASomeWhatLong
foobarfoobarfo
foobarfoobarfo
To solve this, you can use the -Property argument, which needs to be as:
$propertWidth = [int]((Get-Host).ui.RawUI.WindowSize.Width / 3)
$property = #(
#{ Expression = 'LastWriteTime'; Width = $propertWidth; },
#{ Expression = 'Size(KB)'; Width = $propertWidth; },
#{ Expression = 'FullName'; Width = $propertWidth; }
)
... | Format-Table -Property $property -Wrap | ...
If you don't mind having a JSON into your file, you could use:
Get-ChildItem $Path -File -Recurse | Select-Object -Property LastWriteTime, #
{
label = "Size(KB)"
expr = { [string]::Format("{0:0.00}", $_.Length/1KB) }
}, FullName, <some_other_property> | ConvertTo-Json | Out-File $OutputFile
But take into account that ConvertTo-Json has a default Depth of 2. This will also truncate your objects if you have nested objects. But as far as property lengths, it will do fine.

Add a column to a csv file and fill up new column based on an existing column powershell

I have been trying to add a new column to a csv file and populating the new column based on value in an existing column.
I have a table like this:
|name | number | state | desc|
| ---- | ------ |-------|-----|
|a | 1 | n | i |
|b | 2 | n | j |
|c | 3 | l | j |
|d | 4 | m | k |
I want to add a new column data and populate it based on number column matching with an array.
This is my code so far:
$a=("a","b","c")
$b=("p","q","r")
.
.
.
$c= import-csv -Path "C:\..."
$b |where-object {filtered the file based on some criteria}| select-object number, state, desc, #{Name="data"; Expression={Foreach-object {if ($_.number in $a){$_data = "x"}
elseif($_.number in $b){$_.data = "y"}.......} | export-csv -notypeinformation -path "C:\...."
The script runs but do not populate the new column. Please help
You've got the right idea. Import-Csv will produce an array of objects and you can use Select-Object to add calculated properties, then pipe again to Export-Csv. However, it's not exactly clear from the description or the example code what the expression should be. How do you want to define the new "data" property?
For now I'll work with what we have. The array variables $a & $b will never match anything. Also you can't use ForEach-Object like that, nor will assigning to $data work. The returning value of the Expression script block gets assigned to the property you named data. The following example demonstrates the point:
$a = ( "1", "2", "3")
$b = ( "4", "5", "6")
Import-Csv -Path "C:\temp\12-22-20.csv"|
Select-Object number, state, desc,
#{Name = 'Data'; Expression = { If( $_.Number -in $a ){ 'x' } elseif( $_.Number -in $b ){ 'y' } Else { $null }}} |
Export-Csv -Path "C:\temp\12-22-20_New.csv" -NoTypeInformation
The resulting Csv file will look something like:
number state desc Data
------ ----- ---- ----
1 n i x
2 n j x
3 l j x
4 m k y
Update: Example Using Add-Member
You do not need to use a loop to add the property:
$a = ( "1", "2", "3")
$b = ( "4", "5", "6")
Import-Csv -Path "C:\temp\12-22-20.csv" |
Add-Member -MemberType ScriptProperty -Name "data" -Value { If( $this.Number -in $a ){ 'x' } elseif( $this.Number -in $b ){ 'y' } Else { $null }} -PassThru |
Export-Csv -Path C:\temp\12-22-20_New.csv -NoTypeInformation
By using a MemberType of ScriptProperty we can make a slight modification to script block, replacing $_ with $this The pipe is an implicit loop. I'm not sure if there are any detractions to using a ScriptProperty, but this exports as expected. This approach doesn't require storing the output in $c, but -PassThru would facilitate that if preferred.
99% of the time Select-Object is used for this. The only difference I'm aware of it Select-Object converts the objects to PSCustomObjects. Get-Member will preserve the underlying type, however Import-Csv only outputs PSCustomObjects in the first place, so there's no impact here.
Try iterating over the $c array of imported objects and add the new property to all objects. You want to make sure the new column exists in all of the objects. You can either use Select-Object as in your example, or you can use Add-Member to add it to the imported object.
$a=("a","b","c")
$b=("p","q","r")
...
$c = Import-Csv -Path "C:\..."
$c | ForEach-Object {
$value = ""
# custom logic for value of "data"
# if (...) { $value = ... }
$_ | Add-Member -MemberType NoteProperty -Name "data" -Value $value
}
$c | Export-Csv -NoTypeInformation -path "C:\...."

Powershell Replace column data

I have the following command:
$WSUSClients = Get-WsusComputer | Select-Object FullDomainName, OSDescription, LastSyncResult, RequestedTargetGroupName
$WSUSClients | ForEach-Object {$_.FullDomainName -replace ('.corp.com','')} | Set-Content 'C:\Script\WSUS Clients\WSUSclients.csv'
Last part it not correct I want to replace the .corp.com from the column FullDomainname and still leave the other columns but don't get this working.
just use a custom column:
$prop = #{
name = 'FullDomainName'
Expression = {$_.FullDomainName.replace('.corp.com','')}
}
$WSUSClients = Get-WsusComputer | Select-Object $prop, OSDescription, LastSyncResult, RequestedTargetGroupName $WSUSClients | Set-Content 'C:\Script\WSUS Clients\WSUSclients.csv'

PowerShell Import-Csv Issue - Why is my output being treated as a single column and not a CSV?

So I have a CSV file which I need to manipulate a bit, select the data I need and export to another CSV file.
The code I have is:
$rawCSV = "C:\Files\raw.csv"
$outputCSV = "C:\Files\output.csv"
Import-Csv -Header #("a","b","c","d") -Path $rawCSV |
select -Skip 7 |
Where-Object { $_.b.length -gt 1 } |
ft b,a,c,d |
Out-File $outputCSV
So this code uses the Import-Csv command to allow me to select just the columns I need, add some headers in the order I want and then I am simply putting the output in to a CSV file called $outputCSV. The contents of this output file look something like this:
b a c d
- - - -
john smith 29 England
mary poopins 79 Walton
I am not sure what the delimiter is in this output and rather than these columns being treated as individuals, they are treated as just one column. I have gone on further to replace all the spaces with a comma using the code:
$b = foreach ($line in $a)
{
$fields = $line -split '`n'
foreach ($field in $fields)
{
$field -replace " +",","
}
}
Which produces a file that looks like this:
b,a,c,d
john,smith,29,England
mary,poppins,79,Walton
But these are all still treated as one column instead of four separate columns as I need.
* UPDATE *
Using the answer given by #, I now get a file looking like this:
Don't use ft to reorder your columns - it's intended to format output for the screen, not really suitable for CSV.
"Manual" solution:
$rawCSV = "C:\Files\raw.csv"
$outputCSV = "C:\Files\output.csv"
# Import and filter your raw data
$RawData = Import-Csv -Header #("a","b","c","d") -Path $rawCSV
$Data = $RawData | Select -Skip 7 | Where-Object { $_.b.length -gt 1 }
# Write your headers to the output file
"b","a","c","d" -join ',' | Out-File $outputCSV -Force
$ReorderedData = foreach($Row in $Data){
# Reorder the columns in each row
'{0},{1},{2},{3}' -f $Row.b , $Row.a , $Row.c, $Row.d
}
# Write the reordered rows to the output file
$ReorderedData | Out-File $outputCSV -Append
Using Export-Csv:
As of PowerShell 3.0, you could also push the rows into a [pscustomobject] and pipe that to Export-Csv (pscustomobject preserves the order in which you supply the properties):
$rawCSV = "C:\Files\raw.csv"
$outputCSV = "C:\Files\output.csv"
# Import and filter your raw data
$RawData = Import-Csv -Header #("a","b","c","d") -Path $rawCSV
$Data = $RawData | Select -Skip 7 | Where-Object { $_.b.length -gt 1 }
# Take the columns you're interested in, put them into new custom objects and export to CSV
$Data | ForEach-Object {
[pscustomobject]#{ "b" = $_.b; "a" = $_.a; "c" = $_.c; "d" = $_.d }
} | Export-Csv -NoTypeInformation $outputCSV
Export-Csv will take care of enclosing strings in quotes to escape ',' properly (one thing less for you to worry about)
First of all, what your raw CSV file looks like? If it's already like this
john,smith,29,England
mary,poppins,79,Walton
then import-csv will give you an array of objects which you can easily manipulate (and objects are the main reason to use PowerShell ;). For example, to check what you have after import:
$r = Import-Csv -Path $rawCSV -Header #("b","a","c","d")
$r.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
$r[0] | 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()
a NoteProperty System.String a=smith
b NoteProperty System.String b=john
c NoteProperty System.String c=29
d NoteProperty System.String d=England
For now you have array of objects with properties named "a","b","c","d". To manipulate objects you have select-object cmdlet:
$r | Select-Object a,b,c,d
a b c d
- - - -
smith john 29 England
poppins mary 79 Walton
And after all use export-csv to set the output file:
$r | where { $_.b.length -gt 1 } |
select a,b,c,d |
Export-Csv -NoTypeInformation -Encoding utf8 -path $outputCSV
I could think of two possible reasons why your data teated as one column:
consuming application expect different encoding and can't find
delimiters
delimiters are not commas but something else