Powershell: Unable to search imported hashtable - powershell

I'm new to Powershell, and this behavior has me stumped.
Let's say I've created a hashtable with the following information:
$hash= #{
California = 'Sacramento';
Washington = 'Olympia';
Oregon = 'Salem';
Alaska = 'Juneau';
}
$hash
Now I want to search for a particular key using a where -match statement:
$result = #($layers.getEnumerator() | ? {$_.key -match 'Cal'})
$result
This returns with California for the key and Sacramento for the value. All is working as it should.
Now, instead of typing in the keys and values for the table, let's say I want to import them into the hashtable using a CSV file:
California,Sacramento
Washington,Olympia
Oregon,Salem
Alaska,Junea
Here is my code I created to import the CSV, which works fine:
Import-Csv C:\Users\hgordon\Desktop\Maps_to_search\junk.csv -Header "Key","Value"
$layers = #{}
Foreach ($key in $keys) {
$layers[$key] = $csv
}
My problem is that when I run my search statement above, it returns nothing.
Why won't the search work on the imported hashtable? Is there something that I need to modify with the import statement? What is the difference between an imported hashtable vs one that is manually typed?
Thanks!
Edit: I've edited my import script as follows to make it clearer:
$t = Import-Csv -Path C:\Users\hgordon\Desktop\Maps_to_search\junk.csv -Header "Key","Value"
$HashTable = #{}
foreach($r in $t)
{
#Write-Host $r.column1 $r.column2
$HashTable[$r.Key] = $r.Value
}
$HashTable
I still get no result from my search script, though.

To get the job done, you need just to get the output of Import-Csv and use it this way:
$layers = Import-Csv "C:\yourfile.csv" -Header "Key","Value"
$result = #($layers | ? {$_.Key -match 'Cal'})
$result
You don't need to create a HashTable.

So in your current iteration, Import-Csv is importing the CSV as a PSCustomObject type. Your Key/Value headers are being used to create a property on the object with an array under each of the CSV, so you end up with:
key value
--- -----
California Sacramento
Washington Olympia
Oregon Salem
Alaska Junea
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()
key NoteProperty string key=California
value NoteProperty string value=Sacramento
To build your own hashtable from this information, you'd need to merge it yourself:
$HT = #{}
$Csv = Import-Csv ... -Header 'Keys','Values'
For ($i = 0; $i -lt $Csv.Keys.Count; $i++)
{
## Here we're using array-accessors
$HT[$Csv.Keys[$i]] = $Csv.Values[$i]
#alternative
#$HT.($Csv.Keys[$i]) = $Csv.Values[$i]
}

Related

Powershell Pipeline - return a new Object, that was created within pipline

I keep running into the same problem again, and i have my default way of handling it, but it keeps bugging me.
Isn't there any better way?
So basicly i have a pipline running, do stuff within the pipline, and want to return a Key/Value Pair from within the pipline.
I want the whole pipline to return a object of type psobject (or pscustomobject).
Here is the way i do it everytime.
I create a hashtable at the beginning of the pipline and add key/Value Pairs from within the pipline to this hashtable using the .Add() method.
Afterwards i create a psobject by passing the hashtbale to New-Object`s -Property Parameter. This gives me the desired result.
Get-Process | Sort -Unique Name | ForEach-Object -Begin { $ht = #{} } -Process {
# DO STUFF
$key = $_.Name
$val = $_.Id
# Add Entry to Hashtable
$ht.Add($key,$val)
}
# Create PSObject from Hashtable
$myAwesomeNewObject = New-Object psobject -Property $ht
# Done - returns System.Management.Automation.PSCustomObject
$myAwesomeNewObject.GetType().FullName
But this seems a bit cluncky, isn't there a more elegant way of doing it?
Something like this:
[PSObject]$myAwesomeNewObject = Get-Process | Sort -Unique Name | ForEach-Object -Process {
# DO STUFF
$key = $_.Name
$val = $_.Id
# return Key/Val Pair
#{$key=$val}
}
# Failed - returns System.Object[]
$myAwesomeNewObject.GetType().FullName
This unfortunally dosn't work, since the pipe returns an array of hashtables, but i hope you know now what iam trying to achieve.
Thanks
Not sure if this is more elegant but just another way of doing it, this uses an anonymous function so $ht will no longer be available after execution, and casts to [pscustomobject] instead of using New-Object:
[pscustomobject] (Get-Process | Sort -Unique Name | & {
begin { $ht = #{ } }
process {
# DO STUFF
$key = $_.Name
$val = $_.Id
# Add Entry to Hashtable
$ht.Add($key, $val)
}
end { $ht }
})
You can also use the -End parameter to convert the final hash table to a pscustomobject as part of the pipeline, without needing to set the whole thing to a variable
$ht[$key]=$val is also a nice shorthand for $ht.Add($key,$val):
Get-Process |
Sort -Unique Name |
Foreach -Begin { $ht = #{} } -Process {
$ht[$_.Name] = $_.Id
} -End {[pscustomobject]$ht} |
## continue pipeline with pscustomobject
Thanks to #Santiago Squarzon and #Cpt.Whale answers, i were able to combine them to create a solution that pleases me:
$myAwesomeNewObject = `
Get-Process | Sort -Unique Name | & {
begin { $ht = #{} }
process {
# DO STUFF
$key = $_.Name
$val = $_.Id
# Add Entry to Hashtable
$ht[$key]=$val
}
end {[pscustomobject]$ht}
}
# Success - System.Management.Automation.PSCustomObject
$myAwesomeNewObject.Gettype().FullName
# And helper Hashtable is NULL thanks to the
# anonym function
$null -eq $ht
Thanks alot Guys
Alternatively you may create a hashtable using Group-Object -AsHashTable:
# Store the PIDs of all processes into a PSCustomObject, keyed by the process name
$processes = [PSCustomObject] (Get-Process -PV proc |
Select-Object -Expand Id |
Group-Object { $proc.Name } -AsHashtable)
# List all PIDs of given process
$processes.chrome
Notes:
Common parameter -PV (alias of -PipelineVariable) makes sure that we can still access the full process object from within the calculated property of the Group-Object command, despite that we have a Select-Object command in between.
The values of the properties are arrays, which store the process IDs of all instances of each process. E. g. $processes.chrome outputs a list of PIDs of all instances of the chrome process.

Powershell - Using ConvertFrom-csv

I'm brand new to Powershell. I have a variable that contains comma separated values. What I want to do is read each entry in the csv string variable, and assign it to a variable. I am using ConvertFrom-csv to separate the data with headers.
How can I assign each value to a variable, or even better, use ConvertTo-csv to create a new csv string which only has, for example, columns 2/3/6/7 in it?
I would ultimately want to write that data out to a new csv file.
Here is my test code:
#Setup the variable
$Data = "test1,test2,test3,1234,5678,1/1/2021,12/31/2021"
$Data | ConvertFrom-csv -Header Header1,Header2, Header3, Header4, Header5, Header6, Header7
# Verify that an object has been created.
$Data |
ConvertFrom-csv -Header Header1,Header2, Header3, Header4, Header5, Header6, Header7 |
Get-Member
#Show header1
Write-Host "--------Value from $Data----------------------------------------"
$Data[0] #doesn't work, only displays the first character of the string
Write-Host "-----------------------------------------------------------------"
Let me suggest a different approach. If you use ConvertFrom-Csv and assign the result of a variable ($data), this will be an array of Custom Objects. You can run this through a loop that steps through the elements of the array , one at a time, and then through an inner loop that steps through the properties of each object one at a time, setting a variable with the same name as the field header and the same value as the current record's value.
I don't have code that does exactly what you want. But I'm including code that I wrote a few years back that does something similar only using Import-Csv instead of ConverFrom-Csv.
Import-Csv $driver | % {
$_.psobject.properties | % {Set-variable -name $_.name -value $_.value}
Get-Content $template | % {$ExecutionContext.InvokeCommand.ExpandString($_)}
}
Focus on the first inner loop. Each property of the current object will have a name that came from the header and a value that came from the current record of the Csv file. You can ignore the line that says ExpandString. That's just what I choose to do with the variables once they have been defined.
How can I assign each value to a variable, or even better, use ConvertTo-Csv to create a new csv string which only has, for example, columns 2/3/6/7 in it?
This is one way of automating this:
# Define the CSV without headers
$Data = "test1,test2,test3,1234,5678,1/1/2021,12/31/2021"
# Set the number of headers needed
$headers = $Data.Split(',') | ForEach-Object -Begin { $i = 1 } -Process {
"Header$i"; $i++
}
# Set the desired columns we want
$desiredColumns = 2,3,6,7 | ForEach-Object { $_ - 1 } | ForEach-Object {
$headers[$_]
}
# Convert to CSV and filter by Desired Columns
$Data | ConvertFrom-Csv -Header $headers | Select-Object $desiredColumns
Result
Header2 Header3 Header6 Header7
------- ------- ------- -------
test2 test3 1/1/2021 12/31/2021
Result as CSV
$Data | ConvertFrom-Csv -Header $headers |
Select-Object $desiredColumns | ConvertTo-Csv -NoTypeInformation
"Header2","Header3","Header6","Header7"
"test2","test3","1/1/2021","12/31/2021"

Method "split" not found in [Selected.System.Management.Automation.PSCustomObject]

in my script I want to read a csv-file into an array and split the text in the first column.
The file consists a table with 2 columns. In the first column there are the personal names with the short Usernames in brackets. In the second column are the position of the user.
User:
Hoch,Susane (HOCH05)
Albrecht, Melanie (ALBRE05)
Department:
Managment
Salesoffice
I read the first column in an array and want to split every char after the first "(". So the I have got "Hoch, Susanne" instead of "Hoch, Susane (HOCH05)".
I get the following error message:
[Selected.System.Management.Automation.PSCustomObject] contains no method with the name "Split".
The type of the variable "$value is:
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False PSCustomObject System.Object
I canĀ“t find my misstake.
Here is my code:
$Arrayusername_ads_unique = New-Object System.Collections.ArrayList
$AD_User = New-Object System.Collections.ArrayList
$AD_User_table = New-Object System.Collections.ArrayList
$username_AD = New-Object System.Collections.ArrayList
$Arrayusername_ads_unique = Get-Content -Path "C:\temp\Userabgleich\Liste-original\User_ADS-utf8.csv"
$Arrayusername_ads_unique | Out-File C:\temp\Userabgleich\output-temp\User_ADS-utf8.csv -Append -Encoding utf8
$AD_User = Import-CSV 'C:\temp\Userabgleich\output-temp\User_ADS-utf8.csv' -Delimiter ";" | sort User
$AD_User_table = $AD_User | Select-Object User
foreach ($value in $AD_User_table)
{
$value.GetType()
$username_AD = $value.Split("(")
}
You can modify your foreach loop to achieve the results:
foreach ($value in $AD_User_table)
{
($value.user -split "\(")[0]
}
I am splitting on the .user property of $value to retrieve the value you are after in string format. By default, $value is going to be a [PSCustomObject] with a property called User. I am retrieving index 0 ([0]) because your -split match will consume a line of output whether or not you choose to keep the output.
If you are only looping to retrieve this particular result, you can accomplish this without a loop using regex substitution and named captures:
$ad_user_table.user -replace "(?<Name>.*?)\(.*",'${Name}'
since you did not provide a proper CSV to test against, i made a guess at what it would look like. [grin]
what it does ...
uses the .Split() method to split on the (
takes the 1st result of that split
trims away any leading/trailing whitespace
sends the result to the $Names collection
displays the content of that collection
here's the code ...
# fake reading in a CSV file
# in real life, use Import-CSV
$InStuff = #'
User,Department
"Hoch,Susane (HOCH05)","Managment"
"Albrecht, Melanie (ALBRE05)","Salesoffice"
'# | ConvertFrom-Csv
$Names = foreach ($IS_Item in $InStuff)
{
$IS_Item.User.Split('(')[0].Trim()
}
$Names
output ...
Hoch,Susane
Albrecht, Melanie

Export data in the order it was added - PowerShell Export-Csv

I have this code
function get-data()
{
$rec=[PSCustomObject]#()
$DLGP = "" | Select "Name","Grade","Score"
foreach($record in $data)
{
$DLGP.Name=$record.name
$DLGP.Grade=$record.grade
$DLGP.Score=$record.score
$rec += $DLGP
}
return $rec
}
$mydata=get-data
$mydata | Export-Csv -Path $outputPath -NoTypeInformation
The problem is, the data is not exported in the order that I have added it to $rec
How can I get it exported in the order it was added?
Without even using your function, a simple
$mydata = $data | select Name,Grade,Score
would yield the desired result.
Your function is IMO overcomplicated:
$Data = #"
Name,Grade,Score,Dummy
Anthoni,A,10,v
Brandon,B,20,x
Christian,C,30,y
David,D,40,z
"# | ConvertFrom-Csv
function get-data() {
ForEach($record in $data) {
[PSCustomObject]#{
Name =$record.name
Grade=$record.grade
Score=$record.score
}
}
}
$mydata=get-data
$mydata # | Export-Csv -Path $outputPath -NoTypeInformation
Returns here the pretty same order:
Name Grade Score
---- ----- -----
Anthoni A 10
Brandon B 20
Christian C 30
David D 40
LotPings' helpful answer provides effective solutions.
As for what you tried:
By constructing only a single [pscustomobject] instance outside the loop:
$DLGP = "" | Select "Name","Grade","Score"
and then only updating that one instance's properties in each loop iteration:
$DLGP.Name=$record.name
# ....
you effectively added the very same [pscustomobject] instance multiple times to the result array instead of creating a distinct object in each iteration.
Since the same object was being updated repeatedly, that object ended up having the properties of the last object in the input collection, $data.
As an aside:
[PSCustomObject] #() is effectively the same as #(): the [PSCustomObject] is ignored and you get an [object[]] array.
To type the array as one containing [PSCustomObject] instances, you'd have to use an array-typed cast: [PSCustomObject[]] #().
However, given that instances of any type can be cast to [PSCustomObject] - which is really the same as [psobject] - this offers no type safety and no performance benefit.
Also, since your $rec variable isn't type-constrained (it is defined as $rec = [<type>] ... rather than [<type>] $rec = ...) and you're using += to "add to" the array (which invariably requires creation of a new instance behind the scenes), $rec would revert to an [object[]] array after the first += operation.

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