Breaking up a input list, into several columns - powershell

How can I input a list, and have the contents display in multiple columns, say after three items; example:
$carlist = "c:\temp\list-of-cars.txt" # listing 10 cars
$num-cols = 4
Content would be:
Car1
Car2
..
Car9
Car10
$c = $carlist.count/$num-cols # some number of columns
write-host $carlist (something)
Output would be (based on the number of columns:
Car1 Car4 Car7 Car10
Car2 Car5 Car8
Car3 Car6 Car9

Try the Format-Wide cmdlet:
Get-Content cars.txt | Select-Object #{n='name';e={$_}} | Format-Wide -Column 4

$cars = get-content "list-of-cars.txt"
$cars | Format-Wide {$_} -Column 4 -Force
Or, if you prefer the one-liner:
Get-Content .\list-of-cars.txt | Format-Wide {$_} -Column 4 -Force

This is a way:
Function SplitTo-Column($colnum, [object[]]$list)
{
for($i=1; $i -le $list.Length; $i++)
{
if ( ($i % $colnum) -eq 0)
{
[Console]::WriteLine($list[$i-1]) #or write-host "$($list[$i-1])" is more posh
}
elseif ($i -eq $list.count)
{
[Console]::WriteLine($list[$i-1]) #or write-host "$($list[$i-1])"
}
else
{
[Console]::Write($list[$i-1] + "`t" ) #or write-host "$($list[$i-1])`t" -NoNewline
}
}
}
use:
$carlist = gc c:\temp\list-of-cars.txt # listing 10 cars
$numcols = 4
SplitTo-Column -colnum $numcols -list $carlist

Related

Split values table for extract with powershell

I would like to make a new line in my hashtable to extract it in a csv.
I initialize my variable in hastable
$vlr=#{}
$vlr["OS"]=,#("test","test2")
I extract my variable in a .csv
$Output += New-Object PSObject -Property $vlr
$output | Convert-OutputForCSV | export-csv -NoTypeInformation -Delimiter ";" -Path $filepath
and the problem is in the extraction the result of the values ​​is on the same line
My goal is that each value is in a different line
You might want to use the Out-String cmdlet for this:
$vlr=#{}
$vlr["OS"]=,#("test","test2") | Out-String
$Object = New-Object PSObject -Property $vlr
$Object | ConvertTo-Csv
"OS"
"test
test2
"
this solution does not work because in the case where $vlr with several names the extraction will be complicated
$vlr=#{}
$vlr["OS"]=,#("test","test2")
$vlr["PS"]=,#("lous","tique")
it's a problem
https://gallery.technet.microsoft.com/scriptcenter/Convert-OutoutForCSV-6e552fc6
For the function Convert-OutputForCSV
I don't know what the posted function does, but you can make your own function to handle a single-key or multi-key hash table provided all of the key value counts are the same.
function Convert-OutputForCsv {
param(
[parameter(ValueFromPipeline)]
[hashtable]$hash
)
# Array of custom object property names
$keys = [array]$hash.Keys
# Loop through each key's values
for ($i = 0; $i -lt $hash.($keys[0]).count; $i++) {
# Custom object with keys as properties. Property values are empty.
$obj = "" | Select $keys
# Loop through key names
for ($j = 0; $j -lt $keys.Count; $j++) {
$obj.($keys[$j]) = $hash.($Keys[$j])[$i]
}
$obj
}
}
$vlr=[ordered]#{}
$vlr["OS"]='test','test2'
$vlr["PS"]='lous','tique'
$vlr | Convert-OutputForCsv | Export-Csv -NoTypeInformation -Delimiter ";" -Path $filepath
Honestly, if you are in control of the input data, I would just type out a CSV instead of typing out hash tables.
this solution is good in my simplified case but not adapted to my case unfortunately
I'm merging my old base2 array with my new base array and my goal is to concatenate the values ​​in an excel to make them usable
$base2 = Get-content $filepath2 | select -first 1
$base2 = $base2 -split ";"
$base2 = $base2.Replace("`"", "")
$cunt2 = $base2.count - 1
$h2 = ipcsv $filepath2 -Delimiter ";"
$HashTable2 = #{}
for ($i = 0 ; $i -le $cunt2 ; $i++) {
foreach ($r in $h2) {
$HashTable2[$base2[$i]] = $r.($base2[$i])
}
base2 = old tables
$base = Get-content $filepath2 | select -first 1
$base = $base -split ";"
$base = $base.Replace("`"", "")
$cunt = $base.count - 1
$h1 = ipcsv $filepath -Delimiter ";"
$HashTable = #{}
for ($i = 0 ; $i -le $cunt ; $i++) {
foreach ($r in $h1) {
$HashTable[$base[$i]] = $r.($base[$i])
}
New tables $base
once the two arrays are initialized, I merge them and this is where I have to separate the values ​​row by row
$csvfinal = $hashtable, $hashtable2 | Merge-Hashtables

How to set a variable to a column with no header in a tab delimited text file

Barcode1 Plate # 12/29/2017 07:35:56 EST
A 1 4 5 6
A 1 4 5 6
A 1 4 5 6
A 1 4 5 6
A 1 4 5 6
A 1 4 5 6
A 1 4 5 6
Above is an example of a tab delimited text file. I need to get the data from the column with no header; namely, the columns at the end and I don't know how to identify it. I am trying to swap columns and output a text file. The source data file format is the same every time.
This is part of what I have:
$swapColumns = #{
column1 = #{
name = "date-header"
instance = 1
}
column2 = #{
name = "Blank"
instance = 1
}
}
$formats = #(
'XR-{0:yyyyMMdd}-01.txt'
)
$date = [datetime]::now
$ErrorActionPreference = 'Stop'
function Get-HeaderIndex {
param(
[System.Collections.Generic.List[string]]$Source,
[string]$Header,
[uint16]$Instance
)
$index = 0;
for ($i = 0; $i -lt $Instance; $i++) {
$index = $Source.IndexOf($Header, $index, ($Source.Count - $index))
if (($index -eq -1) -or (($i + 1) -eq $Instance)) {
break
}
$index = $index + 1
}
if ($index -eq -1) { throw "index not found" }
return $index
}
#grabs the first item in folder matching UCX-*.txt
$fileDetails = Get-ChildItem $PSScriptRoot\UCX-*.txt | select -First 1
#gets the file contents
$file = Get-Content $fileDetails
#break up script in sections that look like '======section======'
#and store the section name and line number it starts on
$sections = #()
for ($i = 0; $i -lt $file.Count; $i++) {
if ($file[$i] -match '^=+(\w+)=+$') {
$section = $Matches[1]
$sections += [pscustomobject]#{line = $i; header = $section}
}
}
#get the data section
$dataSection = $sections | ? {$_.header -eq 'data'}
#get the section following data
$nextSection = $sections | ? {$_.line -gt $dataSection.line} | sort
-Property line | select -First 1
#get data column headers
$dataHeaders = New-Object System.Collections.Generic.List[string]
$file[$dataSection.line + 1].split("`t") | % {
[datetime]$headerDateValue = [datetime]::MinValue
$headerIsDate = [datetime]::TryParse($_.Replace('EST','').Trim(),
[ref] $headerDateValue)
if ($headerIsDate) {
$dataHeaders.Add('date-header')
}
else {
$dataHeaders.Add($_)
}
}
#get index of columns defined in $swapColumns
$column1 = Get-HeaderIndex -Source $dataHeaders -Header
$swapColumns.column1.name -Instance $swapColumns.column1.instance
$column2 = Get-HeaderIndex -Source $dataHeaders -Header
swapColumns.column2.name -Instance $swapColumns.column2.instance
#iterate over each row in data section, swap data from column1/column2
for ($i = $dataSection.line + 2; $i -lt $nextSection.line - 1; $i++) {
$line = $file[$i]
$parts = $line.split("`t")
$tmp1 = $parts[$column1]
$parts[$column1] = $parts[$column2]
$parts[$column2] = $tmp1
$file[$i] = $parts -join "`t"
}
#write new file contents to files with names defined in $formats
$formats | % {
$file | Out-File ($_ -f $date) -Force
}
If you know what your file format is going to be then forget whatever the current header is and assume when we convert the file to a CSV object.
It looks like you need to parse the date of out the header which should be trivial. Grab it from $fileheader however you would like.
$wholeFile = Get-Content C:\temp\test.txt
$fileHeader = $wholeFile[0] -split "`t"
$newHeader = "Barcode1", "Plate #", "Date", "Plumbus", "Dinglebop"
$wholeFile |Select-Object -Skip 1 | ConvertFrom-Csv -Delimiter "`t" -Header $newHeader
If the columns length is always the same, there's another option, specify manually the width of the columns, See example:
$content = Get-Content C:\temp.tsv
$columns = 13, 24, 35 | Sort -Descending
$Delimiter = ','
$Results = $content | % {
$line = $_
$columns | % {
$line = $line.Insert($_, $Delimiter)
}
$line
} |
ConvertFrom-Csv -Delimiter $Delimiter
Results:
Barcode1 Plate # H1 12/29/2017 07:35:56 EST
--------- ----------- -- -----------------------
A 1 4 5
A 1 4 5
A 1 4 5
A 1 4 5
A 1 4 5
A 1 4 5
A 1 4 5
Then you can easily get the data you need:
$Results[0].H1
4
[This answer doesn't solve the OP's problem after clarifying the exact requirements, but may be of general interest to some, given the question's generic title.]
If the file is really tab-delimited, you can use Import-Csv -Delimiter "`t" to read it, in which case PowerShell will autogenerate header names as H<n> if they're missing, where <n> is a sequence number starting with 1.
Caveat: This doesn't work if the unnamed column is the last one, because - inexplicably - Import-Csv then ignores the entire column (more generally, any run of trailing delimiters).
Import-Csv -Delimiter "`t" file.tsv | Select-Object -ExpandProperty H1

PowerShell : compare 2 excel files or 2 sheets

I have to make a script which can compare 2 excel files or sheets and if one of the cells isn't de the same it tells me which one it is but i don't know how to do this, I watched another situation like this one but i didn't manage to do it can you help me ?
my files are test1.csv and test2.csv
Try this.
$file1 = Import-Csv test1.csv
$file2 = Import-Csv test2.csv
Compare-Object $file1 $file2 -property "HeaderProperty" -IncludeEqual
#Vivek Kumar : Be careful, Compare-Object has a -SyncWindow parameter which has a value by default and that can give only a part of the results.
A very good explanation here : http://community.idera.com/powershell/powershell_com_featured_blogs/b/tobias/posts/tipps-amp-tricks-using-compare-object
One way to bypass this "problem" is to set the -SyncWindow by dividing by 2 the -ReferenceObject :
$file1 = Import-Csv test1.csv
$file2 = Import-Csv test2.csv
Compare-Object -ReferenceObject $file1 -DifferenceObject $file2 -SyncWindow ($file1.length / 2)
Since you mention the files are CSV, you can do all you need with standard PS functions.
However, if using Excel (XLSX/XLS) files, you may be interested in this library: https://github.com/RamblingCookieMonster/PSExcel. Just switch Import-CSV for Import-XLSX.
Below's a very basic example of how this could be done.
Code
function Report-OffendingCell { #NB: doesn't follow good naming conventions
[CmdletBinding()]
param (
[Parameter(Mandatory=$true, ValueFromPipeline = $true)]
[long]$ColumnIndex
,
[Parameter(Mandatory=$true)]
[long]$RowIndex
,
[Parameter(Mandatory=$true)]
[string]$SheetName
,
[Parameter(Mandatory=$false)]
[string]$Explanation
)
process {
#If you want column letters instead of numbers, use something like Convert-NumberToA1 from https://gallery.technet.microsoft.com/office/Powershell-function-that-88f9f690
#"[{0}]!{1}{2}" -f $SheetName, (Convert-NumberToA1 $ColumnIndex + 1), ($RowIndex + 1)
#I've returned an object instead, since that's more useful for any further PS automation
(New-Object -TypeName PSObject -Property #{
ColumnNo = $ColumnIndex + 1
RowNo = $RowIndex + 1
SheetName = $SheetName
Explanation = $Explanation
})
}
}
function Compare-Tables {
[CmdletBinding()]
param (
[Parameter(Mandatory=$true)]
[PSObject[]]$Table1
,
[Parameter(Mandatory=$true)]
[PSObject[]]$Table2
,
[Parameter(Mandatory=$false)]
[string]$Table1Name = 'Table1'
,
[Parameter(Mandatory=$false)]
[string]$Table2Name = 'Table2'
)
begin {
[long]$t1Cols = ($Table1[0].PSObject.Properties | Measure-Object).Count - 1
[long]$t2Cols = ($Table2[0].PSObject.Properties | Measure-Object).Count - 1
[long]$t1Rows = $Table1.Count - 1
[long]$t2Rows = $Table2.Count - 1
[long]$minCols = [System.Math]::Min($t1Cols, $t2Cols)
[long]$maxCols = [System.Math]::Max($t1Cols, $t2Cols)
[long]$minRows = [System.Math]::Min($t1Rows, $t2Rows)
[long]$maxRows = [System.Math]::Max($t1Rows, $t2Rows)
[string]$offendingColTable = if ($maxCols -eq $t1Cols){$Table1Name}else{$Table2Name}
[string]$offendingRowTable = if ($maxRows -eq $t1Rows){$Table1Name}else{$Table2Name}
write-verbose $offendingColTable
write-verbose $offendingRowTable
write-verbose $maxCols
write-verbose $t1Cols
write-verbose $t2Cols
}
process {
0..$minRows | %{ #loop through each row which is populated in both sheets
[long]$row = $_
0..$minCols |
?{(#($Table1[$row].PSObject.Properties)[$_].Value) -ne (#($Table2[$row].PSObject.Properties)[$_].Value)} |
Report-OffendingCell -RowIndex $row -SheetName $Table2Name -Explanation 'Values differ between sheets!' #sheetname could be Table1 or Table2 here; since the cell exists in both sheets
($minCols + 1)..$maxCols | Report-OffendingCell -RowIndex $row -SheetName $offendingColTable -Explanation 'Entire Column only exists on one sheet!'
}
($minRows + 1)..$maxRows | %{ #for any rows which don't exist in one of the sheets, output that
[long]$row = $_
0..$maxCols | Report-OffendingCell -RowIndex $row -SheetName $offendingRowTable -Explanation 'Entire Row only exists on one sheet!'
}
}
}
$test1 = Import-CSV -Path '.\test1.csv'
$test2 = Import-CSV -Path '.\test2.csv'
Compare-Tables -Table1 $test1 -Table2 $test2 -Table1Name 'test1' -Table2Name 'test2' -Verbose | ft SheetName, ColumnNo, RowNo, Explanation
#just so I don't mess up your session with my mock
if((Get-Command Import-Csv).Source -ne 'Microsoft.PowerShell.Utility') {
Remove-Item 'function:Import-Csv'
}
Code for Testing
To provide the example output below, you can use the following code. This overwrites the Import-CSV function with a mocked version of that function which simply returns fixed value data. This code is not required for the real-world scenario; just for those who don't have suitable test CSV files who want something to experiment with.
#region 'Mocked Standard Functions'
#you don't need this function; this is just to make testing simple
function Import-CSV {
param($Path)
switch ($Path) {
'.\test1.csv' {
#(
#{
'Column A Heading'='Row 1 Cell 1';
'Column B Heading'='Row 1 Cell 2';
'Column C Heading'='Row 1 Cell 3';
'Column D Heading'='Row 1 Cell 4';
}
, #{
'Column A Heading'='Row 2 Cell 1';
'Column B Heading'='Row 2 Cell 2';
'Column C Heading'='Row 2 Cell 3';
'Column D Heading'='Row 2 Cell 4';
}
, #{
'Column A Heading'='Row 3 Cell 1';
'Column B Heading'='Row 3 Cell 2';
'Column C Heading'='Row 3 Cell 3';
'Column D Heading'='Row 3 Cell 4';
}
) | %{(New-Object -TypeName PSObject -Property $_)} | select 'Column A Heading', 'Column B Heading', 'Column C Heading', 'Column D Heading' #select needed to ensure columns are returned in the correct order
}
'.\test2.csv' {
#(
#{
'Column Heading 1'='Row 1 Cell 1';
'Column B Heading'='Row 1 Cell 2';
'Column C Heading'='Row 1 Cell 3 difference';
'Column D Heading'='Row 1 Cell 4';
}
, #{
'Column Heading 1'='Row 2 Cell 1';
'Column B Heading'='Row 2 Cell 2';
'Column C Heading'='Row 2 Cell 3';
'Column D Heading'='Row 2 Cell 4';
'Column E Heading'='Row 2 Cell 5 bonus ball!'; #note that though we've not defined on the previous "row", the import function assumes a table, so we'll still have a property on the previous row; only it'll be null
}
) | %{(New-Object -TypeName PSObject -Property $_)}| select 'Column Heading 1', 'Column B Heading', 'Column C Heading', 'Column D Heading', 'Column E Heading' #select needed to ensure columns are returned in the correct order
}
default {throw "no dummy data defined for $Path"}
}
}
#endregion 'Mocked Standard Functions'
Example Output
SheetName ColumnNo RowNo Explanation
--------- -------- ----- -----------
test2 3 1 Values differ between sheets!
test2 5 1 Entire Column only exists on one sheet!
test2 5 2 Entire Column only exists on one sheet!
test1 1 3 Entire Row only exists on one sheet!
test1 2 3 Entire Row only exists on one sheet!
test1 3 3 Entire Row only exists on one sheet!
test1 4 3 Entire Row only exists on one sheet!
test1 5 3 Entire Row only exists on one sheet!
Function Compare-WorkSheet {
<#
.Synopsis
Compares two worksheets with the same name in different files.
.Description
This command takes two file names, a worksheet name and a name for a key column.
It reads the worksheet from each file and decides the column names.
It builds as hashtable of the key column values and the rows they appear in
It then uses PowerShell's compare object command to compare the sheets (explicity checking all column names which have not been excluded)
For the difference rows it adds the row number for the key of that row - we have to add the key after doing the comparison,
otherwise rows will be considered as different simply because they have different row numbers
We also add the name of the file in which the difference occurs.
If -BackgroundColor is specified the difference rows will be changed to that background.
.Example
Compare-WorkSheet -Referencefile 'Server56.xlsx' -Differencefile 'Server57.xlsx' -WorkSheetName Products -key IdentifyingNumber -ExcludeProperty Install* | format-table
The two workbooks in this example contain the result of redirecting a subset of properties from Get-WmiObject -Class win32_product to Export-Excel
The command compares the "products" pages in the two workbooks, but we don't want to register a differnce if if the software was installed on a
different date or from a different place, so Excluding Install* removes InstallDate and InstallSource.
This data doesn't have a "name" column" so we specify the "IdentifyingNumber" column as the key.
The results will be presented as a table.
.Example
compare-WorkSheet "Server54.xlsx" "Server55.xlsx" -WorkSheetName services -GridView
This time two workbooks contain the result of redirecting Get-WmiObject -Class win32_service to Export-Excel
Here the -Differencefile and -Referencefile parameter switches are assumed , and the default setting for -key ("Name") works for services
This will display the differences between the "services" sheets using a grid view
.Example
Compare-WorkSheet 'Server54.xlsx' 'Server55.xlsx' -WorkSheetName Services -BackgroundColor lightGreen
This version of the command outputs the differences between the "services" pages and also highlights any different rows in the spreadsheet files.
.Example
Compare-WorkSheet 'Server54.xlsx' 'Server55.xlsx' -WorkSheetName Services -BackgroundColor lightGreen -FontColor Red -Show
This builds on the previous example: this time Where two changed rows have the value in the "name" column (the default value for -key),
this version adds highlighting of the changed cells in red; and then opens the Excel file.
.Example
Compare-WorkSheet 'Pester-tests.xlsx' 'Pester-tests.xlsx' -WorkSheetName 'Server1','Server2' -Property "full Description","Executed","Result" -Key "full Description"
This time the reference file and the difference file are the same file and two different sheets are used. Because the tests include the
machine name and time the test was run the command specifies a limited set of columns should be used.
.Example
Compare-WorkSheet 'Server54.xlsx' 'Server55.xlsx' -WorkSheetName general -Startrow 2 -Headername Label,value -Key Label -GridView -ExcludeDifferent
The "General" page has a title and two unlabelled columns with a row forCPU, Memory, Domain, Disk and so on
So the command is instructed to starts at row 2 to skip the title and to name the columns: the first is "label" and the Second "Value";
the label acts as the key. This time we interested the rows which are the same in both sheets,
and the result is displayed using grid view. Note that grid view works best when the number of columns is small.
.Example
Compare-WorkSheet 'Server1.xlsx' 'Server2.xlsx' -WorkSheetName general -Startrow 2 -Headername Label,value -Key Label -BackgroundColor White -Show -AllDataBackgroundColor LightGray
This version of the previous command lightlights all the cells in lightgray and then sets the changed rows back to white; only
the unchanged rows are highlighted
#>
[cmdletbinding(DefaultParameterSetName)]
Param(
#First file to compare
[parameter(Mandatory=$true,Position=0)]
$Referencefile ,
#Second file to compare
[parameter(Mandatory=$true,Position=1)]
$Differencefile ,
#Name(s) of worksheets to compare.
$WorkSheetName = "Sheet1",
#Properties to include in the DIFF - supports wildcards, default is "*"
$Property = "*" ,
#Properties to exclude from the the search - supports wildcards
$ExcludeProperty ,
#Specifies custom property names to use, instead of the values defined in the column headers of the TopRow.
[Parameter(ParameterSetName='B', Mandatory)]
[String[]]$Headername,
#Automatically generate property names (P1, P2, P3, ..) instead of the using the values the top row of the sheet
[Parameter(ParameterSetName='C', Mandatory)]
[switch]$NoHeader,
#The row from where we start to import data, all rows above the StartRow are disregarded. By default this is the first row.
[int]$Startrow = 1,
#If specified, highlights all the cells - so you can make Equal cells one colour, and Diff cells another.
[System.Drawing.Color]$AllDataBackgroundColor,
#If specified, highlights the DIFF rows
[System.Drawing.Color]$BackgroundColor,
#If specified identifies the tabs which contain DIFF rows (ignored if -backgroundColor is omitted)
[System.Drawing.Color]$TabColor,
#Name of a column which is unique and will be used to add a row to the DIFF object, default is "Name"
$Key = "Name" ,
#If specified, highlights the DIFF columns in rows which have the same key.
[System.Drawing.Color]$FontColor,
#If specified opens the Excel workbooks instead of outputting the diff to the console (unless -passthru is also specified)
[Switch]$Show,
#If specified, the command tries to the show the DIFF in a Gridview and not on the console. (unless-Passthru is also specified). This Works best with few columns selected, and requires a key
[switch]$GridView,
#If specified -Passthrough full set of diff data is returned without filtering to the specified properties
[Switch]$PassThru,
#If specified the result will include equal rows as well. By default only different rows are returned
[Switch]$IncludeEqual,
#If Specified the result includes only the rows where both are equal
[Switch]$ExcludeDifferent
)
#if the filenames don't resolve, give up now.
try { $oneFile = ((Resolve-Path -Path $Referencefile -ErrorAction Stop).path -eq (Resolve-Path -Path $Differencefile -ErrorAction Stop).path)}
Catch { Write-Warning -Message "Could not Resolve the filenames." ; return }
#If we have one file , we mush have two different worksheet names. If we have two files we can a single string or two strings.
if ($onefile -and ( ($WorkSheetName.count -ne 2) -or $WorkSheetName[0] -eq $WorkSheetName[1] ) ) {
Write-Warning -Message "If both the Reference and difference file are the same then worksheet name must provide 2 different names"
return
}
if ($WorkSheetName.count -eq 2) {$worksheet1 = $WorkSheetName[0] ; $WorkSheet2 = $WorkSheetName[1]}
elseif ($WorkSheetName -is [string]) {$worksheet1 = $WorkSheet2 = $WorkSheetName}
else {Write-Warning -Message "You must provide either a single worksheet name or two names." ; return }
$params= #{ ErrorAction = [System.Management.Automation.ActionPreference]::Stop }
foreach ($p in #("HeaderName","NoHeader","StartRow")) {if ($PSBoundParameters[$p]) {$params[$p] = $PSBoundParameters[$p]}}
try {
$Sheet1 = Import-Excel -Path $Referencefile -WorksheetName $WorkSheet1 #params
$Sheet2 = Import-Excel -Path $Differencefile -WorksheetName $WorkSheet2 #Params
}
Catch {Write-Warning -Message "Could not read the worksheet from $Referencefile and/or $Differencefile." ; return }
#Get Column headings and create a hash table of Name to column letter.
$headings = $Sheet1[-1].psobject.Properties.name # This preserves the sequence - using get-member would sort them alphabetically!
$headings | ForEach-Object -Begin {$columns = #{} ; $i=65 } -Process {$Columns[$_] = [char]($i ++) }
#Make a list of property headings using the Property (default "*") and ExcludeProperty parameters
if ($Key -eq "Name" -and $NoHeader) {$key = "p1"}
$propList = #()
foreach ($p in $Property) {$propList += ($headings.where({$_ -like $p}) )}
foreach ($p in $ExcludeProperty) {$propList = $propList.where({$_ -notlike $p}) }
if (($headings -contains $key) -and ($propList -notcontains $Key)) {$propList += $Key}
$propList = $propList | Select-Object -Unique
if ($propList.Count -eq 0) {Write-Warning -Message "No Columns are selected with -Property = '$Property' and -excludeProperty = '$ExcludeProperty'." ; return}
#Add RowNumber, Sheetname and file name to every row
$FirstDataRow = $startRow + 1
if ($Headername -or $NoHeader) {$FirstDataRow -- }
$i = $FirstDataRow ; foreach ($row in $Sheet1) {Add-Member -InputObject $row -MemberType NoteProperty -Name "_Row" -Value ($i ++)
Add-Member -InputObject $row -MemberType NoteProperty -Name "_Sheet" -Value $worksheet1
Add-Member -InputObject $row -MemberType NoteProperty -Name "_File" -Value $Referencefile}
$i = $FirstDataRow ; foreach ($row in $Sheet2) {Add-Member -InputObject $row -MemberType NoteProperty -Name "_Row" -Value ($i ++)
Add-Member -InputObject $row -MemberType NoteProperty -Name "_Sheet" -Value $worksheet2
Add-Member -InputObject $row -MemberType NoteProperty -Name "_File" -Value $Differencefile}
if ($ExcludeDifferent -and -not $IncludeEqual) {$IncludeEqual = $true}
#Do the comparison and add file,sheet and row to the result - these are prefixed with "_" to show they are added the addition will fail if the sheet has these properties so split the operations
[PSCustomObject[]]$diff = Compare-Object -ReferenceObject $Sheet1 -DifferenceObject $Sheet2 -Property $propList -PassThru -IncludeEqual:$IncludeEqual -ExcludeDifferent:$ExcludeDifferent |
Sort-Object -Property "_Row","File"
#if BackgroundColor was specified, set it on extra or extra or changed rows
if ($diff -and $BackgroundColor) {
#Differences may only exist in one file. So gather the changes for each file; open the file, update each impacted row in the shee, save the file
$updates = $diff.where({$_.SideIndicator -ne "=="}) | Group-object -Property "_File"
foreach ($file in $updates) {
try {$xl = Open-ExcelPackage -Path $file.name }
catch {Write-warning -Message "Can't open $($file.Name) for writing." ; return}
if ($AllDataBackgroundColor) {
$file.Group._sheet | Sort-Object -Unique | ForEach-Object {
$ws = $xl.Workbook.Worksheets[$_]
if ($headerName) {$range = "A" + $startrow + ":" + $ws.dimension.end.address}
else {$range = "A" + ($startrow + 1) + ":" + $ws.dimension.end.address}
Set-Format -WorkSheet $ws -BackgroundColor $AllDataBackgroundColor -Range $Range
}
}
foreach ($row in $file.group) {
$ws = $xl.Workbook.Worksheets[$row._Sheet]
$range = $ws.Dimension -replace "\d+",$row._row
Set-Format -WorkSheet $ws -Range $range -BackgroundColor $BackgroundColor
}
if ($TabColor) {
foreach ($tab in ($file.group._sheet | Select-Object -Unique)) {
$xl.Workbook.Worksheets[$tab].TabColor = $TabColor
}
}
$xl.save() ; $xl.Stream.Close() ; $xl.Dispose()
}
}
#if font colour was specified, set it on changed properties where the same key appears in both sheets.
if ($diff -and $FontColor -and ($propList -contains $Key) ) {
$updates = $diff.where({$_.SideIndicator -ne "=="}) | Group-object -Property $Key | Where-Object {$_.count -eq 2}
if ($updates) {
$XL1 = Open-ExcelPackage -path $Referencefile
if ($oneFile ) {$xl2 = $xl1}
else {$xl2 = Open-ExcelPackage -path $Differencefile }
foreach ($u in $updates) {
foreach ($p in $propList) {
if($u.Group[0].$p -ne $u.Group[1].$p ) {
Set-Format -WorkSheet $xl1.Workbook.Worksheets[$u.Group[0]._sheet] -Range ($Columns[$p] + $u.Group[0]._Row) -FontColor $FontColor
Set-Format -WorkSheet $xl2.Workbook.Worksheets[$u.Group[1]._sheet] -Range ($Columns[$p] + $u.Group[1]._Row) -FontColor $FontColor
}
}
}
$xl1.Save() ; $xl1.Stream.Close() ; $xl1.Dispose()
if (-not $oneFile) {$xl2.Save() ; $xl2.Stream.Close() ; $xl2.Dispose()}
}
}
elseif ($diff -and $FontColor) {Write-Warning -Message "To match rows to set changed cells, you must specify -Key and it must match one of the included properties." }
#if nothing was found write a message which wont be redirected
if (-not $diff) {Write-Host "Comparison of $Referencefile::$worksheet1 and $Differencefile::$WorkSheet2 returned no results." }
if ($show) {
Start-Process -FilePath $Referencefile
if (-not $oneFile) { Start-Process -FilePath $Differencefile }
if ($GridView) { Write-Warning -Message "-GridView is ignored when -Show is specified" }
}
elseif ($GridView -and $propList -contains $key) {
if ($IncludeEqual -and -not $ExcludeDifferent) {
$GroupedRows = $diff | Group-Object -Property $key
}
else { #to get the right now numbers on the grid we need to have all the rows.
$GroupedRows = Compare-Object -ReferenceObject $Sheet1 -DifferenceObject $Sheet2 -Property $propList -PassThru -IncludeEqual |
Group-Object -Property $key
}
#Additions, deletions and unchanged rows will give a group of 1; changes will give a group of 2 .
#If one sheet has extra rows we can get a single "==" result from compare, but with the row from the reference sheet
#but the row in the other sheet might so we will look up the row number from the key field build a hash table for that
$Sheet2 | ForEach-Object -Begin {$Rowhash = #{} } -Process {$Rowhash[$_.$key] = $_._row }
$ExpandedDiff = ForEach ($g in $GroupedRows) {
#we're going to create a custom object from a hash table. We want the fields to be ordered
$hash = [ordered]#{}
foreach ($result IN $g.Group) {
# if result indicates equal or "in Reference" set the reference side row. If we did that on a previous result keep it. Otherwise set to "blank"
if ($result.sideindicator -ne "=>") {$hash["<Row"] = $result._Row }
elseif (-not $hash["<Row"]) {$hash["<Row"] = "" }
#if we have already set the side, this is the second record, so set side to indicate "changed"
if ($hash.Side) {$hash.side = "<>"} else {$hash["Side"] = $result.sideindicator}
#if result is "in reference" and we don't have a matching "in difference" (meaning a change) the lookup will be blank. Which we want.
$hash[">Row"] = $Rowhash[$g.Name]
#position the key as the next field (only appears once)
$Hash[$key] = $g.Name
#For all the other fields we care about create <=FieldName and/or =>FieldName
foreach ($p in $propList.Where({$_ -ne $key})) {
if ($result.SideIndicator -eq "==") {$hash[("=>$P")] = $hash[("<=$P")] =$result.$P}
else {$hash[($result.SideIndicator+$P)] =$result.$P}
}
}
[Pscustomobject]$hash
}
#Sort by reference row number, and fill in any blanks in the difference-row column
$ExpandedDiff = $ExpandedDiff | Sort-Object -Property "<row"
for ($i = 1; $i -lt $ExpandedDiff.Count; $i++) {if (-not $ExpandedDiff[$i].">row") {$ExpandedDiff[$i].">row" = $ExpandedDiff[$i-1].">row" } }
#Sort by difference row number, and fill in any blanks in the reference-row column
$ExpandedDiff = $ExpandedDiff | Sort-Object -Property ">row"
for ($i = 1; $i -lt $ExpandedDiff.Count; $i++) {if (-not $ExpandedDiff[$i]."<row") {$ExpandedDiff[$i]."<row" = $ExpandedDiff[$i-1]."<row" } }
#if we had to put the equal rows back, take them out; sort, make sure all the columns are present in row 1 so the grid puts them in, and output
if ( $ExcludeDifferent) {$ExpandedDiff = $ExpandedDiff.where({$_.side -eq "=="}) | Sort-Object -Property "<row" ,">row" }
elseif ( $IncludeEqual) {$ExpandedDiff = $ExpandedDiff | Sort-Object -Property "<row" ,">row" }
else {$ExpandedDiff = $ExpandedDiff.where({$_.side -ne "=="}) | Sort-Object -Property "<row" ,">row" }
$ExpandedDiff | Update-FirstObjectProperties | Out-GridView -Title "Comparing $Referencefile::$worksheet1 (<=) with $Differencefile::$WorkSheet2 (=>)"
}
elseif ($GridView ) {Write-Warning -Message "To use -GridView you must specify -Key and it must match one of the included properties." }
elseif (-not $PassThru) {return ($diff | Select-Object -Property (#(#{n="_Side";e={$_.SideIndicator}},"_File" ,"_Sheet","_Row") + $propList))}
if ( $PassThru) {return $diff }
}

Pivot/transform a PSObject which contains a list of values?

Have an psobject which is assigned with the following values,
echo "A|B|C|X,A|B|C|Y,A|B|C|Z,D|E|F|X,D|E|F|Y,D|E|F|Z
1,3,5,2,3,7" > 'c:\temp\test.csv'
$d = Import-Csv 'c:\temp\test.csv'
$d | select -first 1
A|B|C|X : 1
A|B|C|Y : 3
A|B|C|Z : 5
D|E|F|X : 2
D|E|F|Y : 3
D|E|F|Z : 7
....
I want it be tranformed to arrays,
A,B,C,1,3,5
D,E,F,2,3,7
....
- - - - - -
X Y Z
Is it a concise way to pivot the psobject? The converting function head can be
function Convert($datarow, [string[]]$pivotCols) { ... }
Convert($d, 'X','Y','Z')
Actually it's one of those rare situations where $d being a PSObject doesn't give you any benefits. Here's an example of how you could achieve what you want, although I'm not certain it's the most concise way:
# Transforming input.
$sInput = "A|B|C|X,A|B|C|Y,A|B|C|Z,D|E|F|X,D|E|F|Y,D|E|F|Z
1,3,5,2,3,7"
$sRawNames, $sRawValues = $sInput -split ([System.Environment]::NewLine)
$cRawNames = $sRawNames -split ','
$cRawValues = $sRawValues -split ','
# Grouping fields by name pattern.
$sGroupingPattern = '\|[XYZ]'
$htGrouped = [Ordered]#{}
for ($i = 0; $i -lt $cNames.Count; $i++) {
$sName = $cRawNames[$i] -replace $sGroupingPattern
try {
$htGrouped.Add($sName, #($cRawValues[$i]))
}
catch [ArgumentException] {
$htGrouped[$sName] += $cRawValues[$i]
}
}
# Formatting output.
$cOutput = #()
$htGrouped.GetEnumerator() | ForEach-Object {
$cValues = ($_.Name -split '\|') + $_.Value
$oValues = New-Object -TypeName PSObject
for ($i = 0; $i -lt $cValues.Count; $i++) {
$oValues | Add-Member -MemberType "NoteProperty" -Name $i -Value $cValues[$i]
}
$cOutput += $oValues
}
$cOutput | Format-Table

Indenting Format-Table output in PowerShell scripts

How can I indent output from the Format-Table cmdlet to a specific column?
I have:
> $SomeValues | Format-Table -HideTableHeaders
A 1
B 2
C 3
But I'd like:
A 1
B 2
C 3
Thanks everyone for your answers. They helped me figure out how to do what I wanted using calculated properties. The Expression must be one less than the amount of indent, due to the automatic single character space between columns in the table.
If you're using the -AutoSize flag:
Write-Host "Not indented"
Write-Host " Indented"
$a = #{ Aa = 1; Bbb = 2; Cccc = 300}
$a | Format-Table -Property #{Expression=" "},Name,Value -AutoSize -HideTableHeaders
If you're not using the -AutoSize flag:
Write-Host "Not indented"
Write-Host " Indented"
$a = #{ Aa = 1; Bbb = 2; Cccc = 300}
$a | Format-Table -Property #{Expression={}; Width=3},Name,Value -HideTableHeaders
The output looks like:
Not indented
Indented
Bbb 2
Aa 1
Cccc 300
Use:
PS> $a = #{A=1; B=2; C=3}
PS> $a.GetEnumerator() | %{ "{0,10}{1,5}" -f $_.key, $_.value }
A 1
B 2
C 3
There's another way that doesn't require creating an additional column. You can simply take the normal output from the Format-Table command, use Out-String to convert it to a string array, and then use ForEach-Object to print out each string with padding. Here's an example using Get-Process.
$indent = " "
(Get-Process svchost) | Format-Table -Property Id, ProcessName | Out-String -Stream | ForEach-Object {Write-Output "$indent$_"}
Actually, you can use ft and -autosize
$a= #{A=1;B=2;C=3}
$a.getenumerator() | ft blah,name,value -hidetableheaders -auto
A 1
B 2
C 3
This should do it
function Indent-ConsoleOutput($output, $indent=4){
if(!($output -eq $null)){
if(!( $indent -is [string])){
$indent = ''.PadRight($indent)
}
$width = (Get-Host).UI.RawUI.BufferSize.Width - $indent.length
($output| out-string).trim().replace( "`r", "").split("`n").trimend()| %{
for($i=0; $i -le $_.length; $i+=$width){
if(($i+$width) -le $_.length){
"$indent"+$_.substring($i, $width)
}else{
"$indent"+$_.substring($i, $_.length - $i)
}
}
}
}
}
'## Get-Process'
Indent-ConsoleOutput ((get-process)[0..5]|format-table) 4
''
'## Log Stye Output'
Indent-ConsoleOutput ((get-process)[0..5]|format-table) " $(Get-Date) "
For a generic solution
function Indent-ConsoleOutput($output, $indent=4){
if(!($output -eq $null)){
if(!( $indent -is [string])){
$indent = ''.PadRight($indent)
}
$width = (Get-Host).UI.RawUI.BufferSize.Width - $indent.length
($output| out-string).trim().replace( "`r", "").split("`n").trimend()| %{
for($i=0; $i -le $_.length; $i+=$width){
if(($i+$width) -le $_.length){
"$indent"+$_.substring($i, $width)
}else{
"$indent"+$_.substring($i, $_.length - $i)
}
}
}
}
}
'## Get-Process'
Indent-ConsoleOutput ((get-process)[0..5]|format-table) 4
''
'## Log Stye Output'
Indent-ConsoleOutput ((get-process)[0..5]|format-table) " $(Get-Date) "
Use:
$SomeValues | Format-Table -HideTableHeaders -AutoSize