PowerShell read column value from csv file [duplicate] - powershell

This question already has answers here:
How can you use an object's property in a double-quoted string?
(5 answers)
Closed 1 year ago.
This is my below input data which is in CSV format and roles,admin, accountant and security are columns.
roles, admin,accountant,security
Engineer, ,x , ,
I want to get value of rows using columns with below code, example , foreach for accountant column should return 'x', but I am getting something else.
$path = "$PSScriptRoot\Test.csv"
$csv = Import-Csv -path $path -Delimiter ","
$columns = $csv | Get-member -MemberType 'NoteProperty' | Select-Object -ExpandProperty 'Name'
$csv | ForEach-Object {
$row = $_
foreach ($col in $columns) {
Write-Host " vaalue of scripting is : $col "
Write-Host " vaalue of scripting row is : $row.$col "
Start-Sleep -s 10
}
}
Output I get is
vaalue of scripting is : accountant
vaalue of scripting row is : #{roles=Engineer; admin=; accountant=x ; security=}.accountant
vaalue of scripting is : admin
vaalue of scripting row is : #{roles=Engineer; admin=; accountant=x ; security=}.admin
vaalue of scripting is : roles
vaalue of scripting row is : #{roles=Engineer; admin=; accountant=x ; security=}.roles
How can I get 'x' for accountant column or any other column value using

Following from my comment, short answer was to use the Subexpression operator $( ), which would allow PowerShell to reference the property $col of the object $row.
Short extract from MS Docs:
Use this when you want to use an expression within another expression. For example, to embed the results of command in a string expression.
To give you a short explanation of why this is needed, using this as an example:
$object = [pscustomobject]#{
foo = 'var'
}
$property = 'foo'
When we do "$object.$property" or in simple terms, "$object.foo", the double quotes "..." are not allowing PowerShell to reference the foo property from $object because the dot . is interpreted as literal and not as a dot method. In addition, the quotes are converting $object to its stringified ? representation #{foo=var} followed by the literal dot . followed by the variable expansion of $property.
Another extract from about_Properties:
The most common way to get the values of the properties of an object is to use the dot method. Type a reference to the object, such as a variable that contains the object, or a command that gets the object. Then, type a dot (.) followed by the property name.
Lastly, what other alternatives do we have to get around this besides $(...):
String Formatting and String.Format method:
'Value of $object.$property is "{0}".' -f $object.$property
[string]::Format('Value of $object.$property is "{0}".', $object.$property)
Using + to concatenate strings is also a very known one:
'Value of $object.$property is "' + $object.$property + '".'
As a side note, and unrelated to the actual issue, this might be a more direct way of approaching your code:
#'
roles,admin,accountant,security
Engineer,,x,,
Operator,,y,,
'# |
ConvertFrom-Csv | ForEach-Object -Begin { $i = 1 } -Process {
foreach($Property in $_.PSObject.Properties.Name)
{
'Value of Row {0} Column "{1}" is "{2}"' -f
$i, $Property, (
'NULL', ($val = $_.$Property)
)[[int][bool]$val]
}
$i++
}
Note the use of .PSObject to access the object's properties and methods, an alternative to Get-Member.
The above would result in:
Value of Row 1 Column "roles" is "Engineer"
Value of Row 1 Column "admin" is "NULL"
Value of Row 1 Column "accountant" is "x"
Value of Row 1 Column "security" is "NULL"
Value of Row 2 Column "roles" is "Operator"
Value of Row 2 Column "admin" is "NULL"
Value of Row 2 Column "accountant" is "y"
Value of Row 2 Column "security" is "NULL"

I wrote this based on your csv file, let me know if it worked (change the folder path and file name).
$folderspath = 'C:\Test'
$csvfilename = 'info.csv'
$csvfilepath = $folderspath + "\" + $csvfilename
$csvfilepath = $csvfilepath.ToString()
$csvfile = Import-CSV -Path $csvfilepath -Delimiter ","
ForEach ($row in $csvfile) {
IF($row.security -eq "High") {
$Roles = $row.roles
$Admin = $row."admin"
$Accountant = $row.accountant
$Security = $row."security"
Write-Host "Roles: " $Roles "; Admin:" $Admin "; Accountant:" $Accountant "; `
Security:" $Security
}
}
The csv file I used
roles, admin,accountant,security
Engineer, ,x , ,
Engineer2,Yes ,x ,High,
Engineer3, No, , Low,

Related

Match Unique Value in Column and put all values on that row in to variables

I'm working with a CSV with 5 Columns, One of the Columns has unique Values.
Fruit, Number, Car, item, color
apple, 2, Chevy, ball, blue
apple, 1, Ford, ball, green
orange, 3, Ford, string, "red,green"
orange, 5, Mazda, key, red
Banana, 4, Tesla, desk, yellow
I need to search for 3 and have it return orange ford string "red,green" as their own variable
i.e. $fruit1 becomes orange $car1 becomes ford $item becomes string and $color bcomes red,green
I can do the search and have it tell me it found 3, but it still just puts runs everything through $fruit1 and if I tell it to write $fruit1 to a file it just get a repeating mess
I Need to Get output to a TXT file like so
for #3
FRUIT=orange
Car=Ford
ITEM = string
COLOR ="red,green"
whith each value in a different part of the file/newline
I can't post from the machine the script is on. So values changed to match my example
Function LogWrite
{
Param ([string]$logstring)
Add-content $Logfile -value $logstring
}
LogWrite "Started execution of script.ps1"
$masterlist = Import-Csv ($filepath + "\" + "masterlistfile.csv" )
$FruitName = #()
$NumberName = #()
$Carname = #()
$ItemName = #()
$Colorname = #()
$masterlist |ForEach-Object {
$FruitName += $_.fruit
$NumberName += $_.number
$Carname += $_.car
$Itemname += $_.item
$Colorname += $_.color
}
$number = 3
$FruitIdentified
$CarIdentified
$ItemIdentified
$ColorIdentified
LogWrite "NUmber $number to be searched in masterlistfile "
if ($NumberName -eq $number)
{
LogWrite "Number found in the list..."
$Where = [array]::IndexOf($NumberName, $number)
LogWrite "Fruit Name : $FruitrName[$Where] "
$FruitIdentified = $FruitName[$Where]
$CarIdentified = $CarName[$Where]
$ItemIdentified = $ItemName[$Where]
}
You can use the following to read your CSV and then export the result with your expected output:
$number = 3
Import-Csv path/to/csv.csv | ForEach-Object {
if($_.number -eq $number) {
"for #$number"
foreach($prop in $_.PSObject.Properties.Name -ne 'Number') {
'{0}={2}{1}{2}' -f $prop, $_.$prop, ($null, '"')[$prop -eq 'color']
}
}
} | Set-Content path/to/file.ext
Note that Set-Content will overwrite the export file, if you want to append you would use Add-Content as in your function.
To give some context on what the code does:
Read the CSV and convert it to an object with Import-Csv
Loop over all objects and filter where the value of the Number property is equal to $number.
Output for #$number, in this example would be for #3".
Get all properties of the object using PSObject.Properties.Name and exclude the Number property using -ne 'Number'.
Loop over the Property Names and output '{0}={1}' -f $prop, $_.$prop, here we use the Format Operator -f, {0} would be the Property Name and {1} would be the Property Value. {2} will wrap the value with ".." if the Property Name is color.
The output you would be getting using your CSV for input would be:
for #3
Fruit=orange
Car=Ford
item=string
color="red,green"

Rename columns in Select-Object

I have an object with spaces in their properties names. I want to Select-Object #{n='NewName';e={$_.'Old Name'}} for every NoteProperty. Since there is a lot of them, I created this function. Running this code will return an array of hash tables but I can't get the old name $col to be replaced with the actual old name. I think it's because it's bound to a new execution context but I can't make it to work.
function Rename-Columns {
# Array of HashTable splat to rename columns in Select-Object (TSQL SELECT [col with space] as colwithspace)
param (
[string[]]$Columns,
[hashtable]$RenamePattern = #{Replace='\s|\:|\.|\+|\|';With=''} # remove unwanted cars
)
$return = #()
foreach ($col in $Columns) {
$newName = $col -replace $RenamePattern['Replace'], $RenamePattern['With']
$return += #{n="$newName";e={$_.'"$col"'}} # <- can't replace $col
}
$return
}
Your current approach can't work because by the time you pass the {$_."$col"} block to Select-Object, $col no longer resolves to the value it did when you created the scriptblock inside the loop.
In order to bind the current iterator value of $col to the expression block, you need a closure:
function Rename-Columns {
# Array of HashTable splat to rename columns in Select-Object (TSQL SELECT [col with space] as colwithspace)
param (
[string[]]$Columns,
[hashtable]$RenamePattern = #{Replace='\s|\:|\.|\+|\|';With=''} # remove unwanted cars
)
foreach ($col in $Columns) {
# calculate new name
$newName = $col -replace $RenamePattern['Replace'], $RenamePattern['With']
# close over `{$_.$col}` to bind the current value to `$col`
#{n=$newName;e={$_.$col}.GetNewClosure()}
}
}
GetNewClosure() will see that the $col variable exists in the scope where it was called, and so it copies its value and stores it along-side the scriptblock.
As a result, when Select-Object executes the property expression (at a later time), the scriptblock "remembers" the original value and $col resolves correctly/as expected.
With sample data:
$data = [pscustomobject]#{
'Old Name' = 123
'Schema Name' = 456
}
$originalColumnNames = $data |Get-Member -MemberType Properties |ForEach-Object -MemberName Name
$data |Select -Property #(Rename-Columns $originalColumnNames)
It yields the expected result (spaces replaced in all names):
OldName SchemaName
------- ----------
123 456

How to concatenate array of integers into comma separated string

I have two question, acually:
How to join array of integers into comma separated string?
(1,2,3) => "1,2,3"
How to convert array of integers to array of string? (1,2,3) => ("1", "2", "3")
$arraylist = New-Object 'system.collections.arraylist'
$arraylist.Add(1);
$arraylist.Add(2);
$csv = ??
#($arraylist-join -',') returns error: Cannot convert value "," to type "System.Int32". Error: "Input string was not in a correct format."
In your question, you've commented out the following snippet:
($arraylist-join -',')
because it returns the error Cannot convert value "," to type "System.Int32"...
The reason for this is the dash - in front of ','.
In PowerShell, only operators and parameters are prefixed with a dash, and since ',' is neither (it's an argument to an operator), the PowerShell parser gets super confused and tries to treat -',' as a value expression that would result in a negative number.
Just void the dash and you'll be fine:
$arraylist -join ','
Finally, you can easily cast an array of integers to an array of strings with the unchecked cast operator -as (PowerShell 3.0 and newer):
$StringArray = 1,2,3,4,5 -as [string[]]
or with an explicit cast (PowerShell 2.0-compatible):
$StringArray = [string[]]#(1,2,3,4,5)
Next code snippet could help out on understanding:
$arraylist = New-Object 'system.collections.arraylist'
$arraylist.Add(111) | Out-Null
$arraylist.Add([string]222) | Out-Null
$arraylist.Add('"' + 3 + '"') | Out-Null
for($i=0; $i -lt $arraylist.Count; $i++ ){
write-host $i, $arraylist[$i], $arraylist[$i].GetType()
}
write-host ''
$csv = $arraylist -join ','
$csv
Output:
0 111 System.Int32
1 222 System.String
2 "3" System.String
111,222,"3"
Additional view of (un)importance of " double quotes in a string type shows next + operation (sum of integers but concatenation of strings):
write-host $i, $arraylist[$i], $arraylist[$i].GetType().Name, ($arraylist[$i] + 55)
gives next output:
0 111 Int32 166
1 222 String 22255
2 "3" String "3"55
111,222,"3"
However, " double quotes have another important and meaningful role in .csv file when imported e.g. to Excel sheet.
This worked for me:
[String]::Join(",", $arraylist.ToArray())
I got 1,2.
And then the second part:
foreach($number in $arraylist) { $number.ToString() }
I don't if this is correct. Usually, they have powershell2. Just give this a little try.
$a = #()
$strArrayNum=""
for($i=0; $i -lt $arraylist.length; $i++ ){
$strArrayNum += $element
$strArrayNum = $i+1 -eq $arraylist.length ? "" : ","
}
$a = $strArrayNum.Split(",")

Powershell fixed width export

I am having a text file wich uses fixed width for separating columns.
I'm loading the file and create a new column which concatinates the values of the first two columns.
The problem I have that when exporting the data I need to define a fixed column width of 13 for Column C.
Column A (3) Column B(9) Column C(13)
MMA 12345 12345_MMA
MMO 987222 987222_MMO
Basically for this example in the export I am missing 4 spaces for the first row and 3 for the second row.
Thisis my current code, which also includes a new row for MD5 creation.
# Load input data
$PreSystem = [IO.File]::ReadAllText("C:\FILE.txt")
# Initiate md5-hashing
$md5 = new-object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider
$utf8 = new-object -TypeName System.Text.UTF8Encoding
# Split input data by lines
$all = $PreSystem.split("`n")
# Loop over lines
for($i = 0; $i -lt $all.length-1; $i += 1) {
# Access distinct lines
$entry = "$($all[$i])"
# Get the different parameters
$market_code = $entry.substring(1,3)
$soc = $entry.substring(4,9)
# Hash the SOC element
$hash = [System.BitConverter]::ToString($md5.ComputeHash($utf8.GetBytes($soc)))
# Create desired format for each entry
$output = $hash.Replace("-","")+$soc.Replace(" ","") + "_" + $market_code + $all[$i]
# Write to file
"$output" | Out-File -Filepath C:\"C:\FILE.txt" -Append -encoding ASCII
}
Thanks in advance
You can create a custom table format using the tip explained here. Here is an example for Get-Process:
$a = #{Expression={$_.Name};Label="Process Name";width=25}, `
#{Expression={$_.ID};Label="Process ID";width=15}, `
#{Expression={$_.MainWindowTitle};Label="Window Title";width=40}
Get-Process | Format-Table $a
Basically, you build an expression through wich Format-Table will pipe
each row. Instead of taking care of the formating yourself for each row, you build a hash and pipe it through Format-Table.
It's still not quite clear to me what output you actually want to achieve, but maybe this will give you some idea.
One of the most convenient ways to get formatted string output is using the format operator (-f). You specify a format string with placeholders in curly brackets, and fill it with the values of an array:
PS C:\> '_{0}:{1}:{2}_' -f 'foo', 'bar', 'baz'
_foo:bar:baz_
Column widths can be specified in the format string like this:
PS C:\> '_{0,-5}:{1,7}:{2,-9}_' -f 'foo', 'bar', 'baz'
_foo : bar:baz _
As you can see, negative values align the column to the left, positive values align it to the right.
If there's a chance that a value is too long for the give column width you need to truncate it, e.g. with the Substring() method:
PS C:\> $s = 'barbarbar'
PS C:\> $len = [math]::Min(7, $s.Length)
PS C:\> '_{0,-5}:{1,7}:{2,-9}_' -f 'foo', $s.Substring(0, $len), 'baz'
_foo :barbarb:baz _
You can quickly have a fixed size left-aligned content string using the following code:
Write-Host "$myvariable $(" " * 60)".Substring(0,60)
this will give you a fixed width of 60 characters with the contents aligned to the left
One of the solutions is for each of the rows use this mechanism when concatenating:
$a = "MMA"
$b = "12345"
$str = "$($b)_$($a)"
if (($str.Length) -ge 13 ) {
Write-Host "$($str)"
} else {
$padStr = " " * (13 - ($str.Length))
Write-Host "$($str)$($padStr)"
}
So instead of Write-Host CmdLet you can use the appropriate CmdLet for your purpose.
Edit, after adding actual code. So the above logic would translate into:
$market_code = $entry.subString(1,3)
$soc = $entry.subString(4,9)
$str = $soc.Replace(" ", "") + "_" + $market_code
if (($str.Length) -ge 13 ) {
$output = $hash.Replace("-","") + $str + $all[$i]
} else {
$padStr = " " * (13 - ($str.Length))
$output = $hash.Replace("-","") + $str + $padStr + $all[$i]
}
You can do fixed size using next code:
$data = "Some text"
$size = 20
$str = [string]::new(' ',$size).ToCharArray()
$data.CopyTo(0,$str,0,$data.Length)
$str = $str -join ''

How do I loop through a dataset in PowerShell?

I am trying to output values of each rows from a DataSet:
for ($i=0;$i -le $ds.Tables[1].Rows.Count;$i++)
{
Write-Host 'value is : ' + $i + ' ' + $ds.Tables[1].Rows[$i][0]
}
gives the output ...
value is : +0+ +System.Data.DataSet.Tables[1].Rows[0][0]
value is : +1+ +System.Data.DataSet.Tables[1].Rows[1][0]
value is : +2+ +System.Data.DataSet.Tables[1].Rows[2][0]
value is : +3+ +System.Data.DataSet.Tables[1].Rows[3][0]
value is : +4+ +System.Data.DataSet.Tables[1].Rows[4][0]
value is : +5+ +System.Data.DataSet.Tables[1].Rows[5][0]
value is : +6+ +System.Data.DataSet.Tables[1].Rows[6][0]
How do I get the actual value from the column?
The PowerShell string evaluation is calling ToString() on the DataSet. In order to evaluate any properties (or method calls), you have to force evaluation by enclosing the expression in $()
for($i=0;$i -lt $ds.Tables[1].Rows.Count;$i++)
{
write-host "value is : $i $($ds.Tables[1].Rows[$i][0])"
}
Additionally foreach allows you to iterate through a collection or array without needing to figure out the length.
Rewritten (and edited for compile) -
foreach ($Row in $ds.Tables[1].Rows)
{
write-host "value is : $($Row[0])"
}
Here's a practical example (build a dataset from your current location):
$ds = new-object System.Data.DataSet
$ds.Tables.Add("tblTest")
[void]$ds.Tables["tblTest"].Columns.Add("Name",[string])
[void]$ds.Tables["tblTest"].Columns.Add("Path",[string])
dir | foreach {
$dr = $ds.Tables["tblTest"].NewRow()
$dr["Name"] = $_.name
$dr["Path"] = $_.fullname
$ds.Tables["tblTest"].Rows.Add($dr)
}
$ds.Tables["tblTest"]
$ds.Tables["tblTest"] is an object that you can manipulate just like any other Powershell object:
$ds.Tables["tblTest"] | foreach {
write-host 'Name value is : $_.name
write-host 'Path value is : $_.path
}
The parser is having trouble concatenating your string. Try this:
write-host 'value is : '$i' '$($ds.Tables[1].Rows[$i][0])
Edit: Using double quotes might also be clearer since you can include the expressions within the quoted string:
write-host "value is : $i $($ds.Tables[1].Rows[$i][0])"