I am using hashtables in powershell for multiple entries my hastable is working fine, but for the single entry its throwing the error by not picking the value it meant to be. Suppose I am having a Line
Aman;Indore;789858585;23
Raman;Delhi;785458545;35
for this two entry, my hashtable is working fine when i am giving command for example userinput.name[0] so it will pick Aman as the value and user.input.name[1] then it picks Raman as the value.
But the same code with single entry of
Aman;Indore;789858585;23
when I am giving userinput.name[0] so it is picking up A. The first letter of Aman it's picking instead of the complete name.
You might not even realise it, but you're using a PowerShell feature called Member Enumeration which was introduced in PowerShell 3.0.
Basically, if you attempt to access a named property on an array, if the property doesn't exist on the array object itself then PowerShell will look for that property on all the items in the array and return those values in a new array instead.
For example:
PS> $csv = #"
Aman;Indore;789858585;23
Raman;Delhi;785458545;35
"#
PS> $data = $csv | ConvertFrom-Csv -Delimiter ";" -Header #("Name", "Location", "Mobile", "Age");
PS> $data
Name Location Mobile Age
---- -------- ------ ---
Aman Indore 789858585 23
Raman Delhi 785458545 35
PS> $data.Name
Aman
Raman
# note this returns an array of objects thanks to member enumeration
PS> $data.Name.GetType().FullName
System.Object[]
PS> $data.Name[0]
Aman
In your case, $data.Name returns a new array containing the value of the Name property on all the items in $data - effectively #("Aman", "Raman"). So, when you use $data.Name[0], you're retrieving the first item in the array created by member enumeration - i.e. Aman - and all's well in the world...
Now, the catch is that if there's only one item in the array created by member enumeration it gets "unrolled" to be the value of the first item:
PS> $csv = #"
Aman;Indore;789858585;23
"#
PS> $data = $csv | ConvertFrom-Csv -Delimiter ";" -Header #("Name", "Location", "Mobile", "Age");
PS> $data
# Name Location Mobile Age
# ---- -------- ------ ---
# Aman Indore 789858585 23
# note this returns a single string because the "member enumeration" array
# gets unrolled if there's only one item
PS> $data.Name.GetType().FullName
System.String
PS> $data.Name
# Aman
PS> $data.Name[0]
# A
And in your case the $data.Name[0] is equivalent to "Aman"[0] which returns A.
To fix this, rather than inadvertently use member enumeration by doing $data.Name[0], where the result can vary based on the number of items in the array, you can use one of the following:
PS> $data[0].Name
Aman
PS> #($data.Name)[0]
Aman
The first option is probably more performant in the general case, but the second is a useful workaround in some cases where the first won't work (e.g. when dealing with return values from functions / where-object, etc).
Related
I am new to PowerShell and trying to get a list of VM names and their associated IP Addresses from within Hyper-V.
I am getting the information fine on the screen but when I try to export to csv all I get for the IP Addresses is System.Collections.Generic.List`1[System.String] on each line.
There are suggestions about "joins" or "ConvertTo-CSV" but I don't understand the syntax for these.
Can anyone help?
This is the syntax I am using...
Get-VM | Select -ExpandProperty VirtualNetworkAdapters | select name, IPV4Addresses | Export-Csv -Path "c:\Temp\VMIPs.csv"
If an object you export as CSV with Export-Csv or ConvertTo-Csv has property values that contain a collection (array) of values, these values are stringified via their .ToString() method, which results in an unhelpful representation, as in the case of your array-valued .IPV4Addresses property.
To demonstrate this with the ConvertTo-Csv cmdlet (which works analogously to Export-Csv, but returns the CSV data instead of saving it to a file):
PS> [pscustomobject] #{ col1 = 1; col2 = 2, 3 } | ConvertTo-Csv
"col1","col2"
"1","System.Object[]"
That is, the array 2, 3 stored in the .col2 property was unhelpfully stringified as System.Object[], which is what you get when you call .ToString() on a regular PowerShell array; other .NET collection types - such as [System.Collections.Generic.List[string]] in your case - stringify analogously; that is, by their type name.
Assuming you want to represent all values of an array-valued property in a single CSV column, to fix this problem you must decide on a meaningful string representation for the collection as a whole and implement it using Select-Object with a calculated property:
E.g., you can use the -join operator to create a space-separated list of the elements:
PS> [pscustomobject] #{ col1 = 1; col2 = 2, 3 } |
Select-Object col1, #{ n='col2'; e={ $_.col2 -join ' ' } } |
ConvertTo-Csv
"col1","col2"
"1","2 3"
Note how array 2, 3 was turned into string '2 3'.
OtherObjectPipedStuff | Select-object name,IPV4Addresses | export-csv PP.csv -NoTypeinformation
I have an .exe console program which put the result into the console in the following format:
------------------ ----------- ----------------
CompanyName CompanyId CompanyType
------------------ ----------- ----------------
test1 1 Root
test2 2 Center
test3 3 Company
------------------ ----------- ----------------
I would like to pick up this in a PowerShell script and filter by the CompanyName.
I tried it with:
MyTool.exe companies | where {$_.CompanyName -eq 'test1'}
but it seems that this doesn't work.
Here is one way to convert the output of an EXE to a powershell collection of objects. what it does ...
creates a fake version of the output of your exe file
filters out the lines with repeated hyphens
replaces leading spaces with nothing
replaces 2-or-more spaces with a comma
converts that CSV-like string array into a collection of powershell objects
here's the code [grin] ...
# fake getting string output from an EXE
$InStuff = #'
------------------ ----------- ----------------
CompanyName CompanyId CompanyType
------------------ ----------- ----------------
test1 1 Root
test2 2 Center
test3 3 Company
------------------ ----------- ----------------
'# -split [environment]::NewLine
$CompanyInfo = $InStuff -notmatch '--{2,}' -replace '^ {1,}' -replace ' {2,}', ',' |
ConvertFrom-Csv
$CompanyInfo
'=' * 30
$CompanyInfo -match 'Test1'
output ...
CompanyName CompanyId CompanyType
----------- --------- -----------
test1 1 Root
test2 2 Center
test3 3 Company
==============================
test1 1 Root
PowerShell reports an external program's output as an array of lines (strings).
To filter such output using string parsing, use the -match operator:
# Extract the line of interest with -match and a regex
PS> #(MyTool.exe companies) -match '^\s+test1\s'
test1 1 Root
Note:
#(...), while not strictly necessary here, ensures that MyTool.exe's output becomes an array even if it happens to output just one line, so that -match performs filtering on that array (with a scalar LHS, -match returns a Boolean).
Regex ^\s+test1\s matches one or more (+) whitespace characters (\s) at the start of each line (^), followed by literal test1, followed by a whitespace character - thereby limiting matching to the CompanyName column.
If you want to parse the result into individual fields:
# Extract the line of interest with -match and a regex,
# then split that line into whitespace-separated tokens and store
# them in individual variables.
PS> $name, $id, $type = -split (#(MyTool.exe companies) -match '^\s+test1\s')
PS> $name, $id, $type
test1
1
Root
Lee Dailey's answer:
shows you how to instead parse your external program's output into custom objects whose properties you can query, by first transforming your program's output into CSV text and then parsing that into custom objects via ConvertFrom-Csv.
While this is very much in the spirit of PowerShell, you inevitably pay a performance penalty, and for extracting simple substrings it may not be worth it.
then, regrettably, forgoes the advantages of having parsed the input into objects by reverting to string matching that negates the benefits of having property-individual matching at one's disposal:
applying -match - a string operator - to a custom object LHS results in a hashtable-like representation for display that is not suited to programmatic processing; e.g.: #{CompanyName=test1; CompanyId=1; CompanyType=Root}
therefore - speaking in the abstract - using -match can result in false positives - because the matching isn't limited to the property of interest.
In short: If you went to the trouble of parsing the input into objects - if necessary at all - use their properties for robust filtering, as you attempted in your question:
$CompanyInfo | where {$_.CompanyName -eq 'test1'}
or, more succinctly, using PSv3+ syntax:
$CompanyInfo | where CompanyName -eq test1
or, more efficiently, in PSv4+, using the .Where() array method:
$CompanyInfo.Where({ $_.CompanyName -eq 'test1'})
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.
I am attempting to put the output of compare-object. I am new to Powershell and unfortunately don't know the ins and outs yet.
My command is as follows:
Compare-Object -referenceObject $(Get-Content "c:\temp\mek\123-first.txt") -differenceObject $(Get-Content "c:\temp\mek\123-second.txt") | %{$_.Inputobject} | sort-object | out-file "c:\temp\mek\results.txt"
The contents of my files are as follows (simply comparing Windows services):
systemname name state startmode
---------- ---- ----- ---------
D7MCYP AdobeARMservice Stopped Auto
D7MCYP AdobeFlashPlayerUpdateSvc Stopped Manual
D7MCYP AeLookupSvc Stopped Manual
My results of the compare-object are as follows:
BL3C4V wudfsvc Stopped Auto
BL3C4V wudfsvc Stopped Manual
D7MCYP AdobeARMservice Running Auto
D7MCYP AdobeARMservice Stopped Auto
Now if anyone could help output to keep the first 2 columns per server and the different values of columns 3,4 to new columns (5,6). It would also be nice if I get titles too. For example:
Server Service Before State Before Mode After State After Mode
BL3C4V wudfsvc Stopped Auto Stopped Manual
D7MCYP AdobeARMservice Running Auto Stopped Auto
Note: The code below is an exercise in parsing plain-text data into objects for more robust, flexible handling.
Ideally, however, processing should start out with objects rather than plain text,
which is why starting with PowerShell cmdlets such as Get-Service rather than the text output from external utilities is preferable.
Assuming that all entries in each input file have a matching server + service-name entry in the respective other file:
$f1, $f2 = "c:\temp\mek\123-first.txt", "c:\temp\mek\123-second.txt"
Compare-Object (Get-Content $f1) (Get-Content $f2) | ForEach-Object {
$i = 0; $ht = #{}; $vals = -split $_.InputObject
foreach($col in 'Server', 'Service', 'State', 'Mode') {
$ht.$col = $vals[$i++]
}
$ht.Before = $_.SideIndicator -eq '<='
[pscustomobject] $ht
} | Group-Object Server, Service | ForEach-Object {
$ndxBefore, $ndxAfter = if ($_.Before) { 0, 1 } else { 1, 0 }
[pscustomobject] #{
Server = $_.Group[0].Server
Service = $_.Group[0].Service
'State Before' = $_.Group[$ndxBefore].State
'Mode Before' = $_.Group[$ndxBefore].Mode
'State After' = $_.Group[$ndxAfter].State
'Mode After' = $_.Group[$ndxAfter].Mode
}
} | Sort-Object Server, Service |
Format-Table
Note:
The above formats the output for display (using Format-Table), without sending it to a file.
You can append | Out-File "c:\temp\mek\results.txt" to save the same representation to a file.
However, note that the command - before Format-Table is applied - returns objects with individual properties, so you can output to a file in a variety of formats, such as by using Export-Csv, for instance.
Example output:
Server Service State Before Mode Before State After Mode After
------ ------- ------------ ----------- ----------- ----------
D7MCYP AdobeFlashPlayerUpdateSvc Stopped Manual Stopped Auto
D7MCYP AeLookupSvc Stopped Manual Started Manual
Explanation:
A single, long pipeline is used, which makes the code concise and memory-efficient.
The pipeline breaks down as follows:
Comparison:
Compare-Object compares the array of lines from the two input files returned by the Get-Content calls, and outputs [pscustomobject] instances representing the differences found, with string property .SideIndicator indicating whether the line at hand (accessible via .InputObject) is unique to the LHS (the 1st input file) - <= - or the RHS (2nd input file) - >=
Transformation to custom objects:
The script block ({ ... }) passed to ForEach-Object is executed for each input object (represented as $_).
-split $_.InputObject splits the "difference line" at hand into fields by runs of whitespace and stores the resulting fields as an array in $vals.
$ht is an auxiliary hashtable that is used to map the field values to field names.
$ht.Before adds a Boolean entry to indicate whether the difference line at hand is from the "before file" (the 1st input file) or not.
[pscustomobject] $ht converts the aux. hashtable into a custom object and outputs it (sends it through the pipeline).
Grouping:
Group-Object is used to group the resulting objects can by shared Server and Service property values, resulting in a [Microsoft.PowerShell.Commands.GroupInfo] instance representing each grouping.
Transformation to combined custom objects:
Again, ForEach-Object is used to perform per-input object processing.
[pscustomobject] #{ ... } is used to construct each combined output object, again using an auxiliary hashtable.
$_.Group contains the input objects that form each group - in our case, $_.Group[0] and $_.Group[1] are the converted-to-objects input lines representing a given server-service combination.
By definition, both input objects have the same .Server and .Service values, so blindly using $_.Group[0]'s values for the combined output object will do.
By contrast, the * Before and * After properties the appropriate input object (whether from the 1st or 2nd file), which is why the array indices $ndxBefore and $ndxAfter are chosen accordingly, via the previously added .Before property
Sorting:
Sort-Object sorts the resulting objects by the specified properties.
Output formatting:
Output-formatting cmdlet Format-Table ensures that the sorted objects are presented as a table.
I have a csv with a list of usernames
I want to import just one cell of the csv file e.g. A2
Is it possible to be that specific? I have tried googling for this but don't see an exact solution. Tried powershell help also.
Can this be done ?
Thanks
Confuseis
The below example will select and output only 'cell' A2 from test.csv
In this example, the column header for row A is 'username'
$inFile = Import-Csv c:\Temp\test.csv
$targetCell = $inFile.username[1]
Write-Output $targetCell
This snippet is doing the following:
Import the csv file, yielding a PowerShell object.
Select the column you want to work with, the items from that column can be treated as an array. Select the desired item in that column by referring to it's zero based index value.
Output the results.
Import-CSV creates an array of objects from the input file. The column labels in the first row of the CSV become the property names. The other rows are objects in the array. Like any array you can call one element using brackets.
$arrUsers = Import-CSV c:\temp\users.csv
$arrUsers[1]
The second command, above, prints the second object, since counting starts with 0. This object came from the third line of the CSV file, since the first was used as column headers.
If you use Get-Member, it will show you the members (properties and methods) of an object.
$arrUsers | Get-Member
Assuming one of the members is username, combine this with array indexing, you can use:
$arrUsers[1].username
Import-CSV is a very flexible tool. Especially combined with Foreach and Export-CSV. Between Get-Help and Get-Member, you can explore Powershell with ease. Good luck.
When you use Import-Csv you convert the content into a PSCustomObject.
Examples on the following table:
PS> $csv = Import-Csv .\test.csv
PS> $csv
ProcessName Id WS CPU
----------- -- -- ---
sihost 5996 30015488 44.640625
pia_nw 11064 10620928 52.921875
pia_nw 2344 7933952 104.0625
RuntimeBroker 6500 77500416 177.34375
SettingSyncHost 6736 5074944 202.796875
explorer 6600 284934144 272.140625
ipoint 920 3162112 372.78125
rubyw 10648 18026496 389.46875
pia_nw 3108 31330304 1640.5625
OneDrive 10208 33206272 6422.4375
So you will need a NoteProperty name to call a value you're looking for.
PS> $csv.ProcessName[0]
sihost
Another way is to make a header array and use that to slice the data.
If working with a an object:
PS> $header = ($csv | ConvertTo-Csv -NoTypeInfo)[0] -replace '"' -split ",";
>>
PS> $header
ProcessName
Id
WS
CPU
Or if working with the file:
PS> $header = (gc .\test.csv)[0] -replace '"' -split ',';
ProcessName
Id
WS
CPU
Then just use the appropriate index:
PS> $csv[0]."$($header[0])"
sihost
Finally there is the Excel.Application ComObject method on an xlsx file. This will let you select cell's and ranges.
PS> $file = "C:\Some\Path\IMade\Up\test.xlsx"
PS> $objExcel = New-Object -ComObject Excel.Application
PS> $objExcel.Visible = $false
PS> $wb = $objExcel.Workbooks.Open($file)
PS> $ws = $wb.Sheets.Item(1)
PS> $ws.Range("A2").Text
sihost
More info on using the ComObjects can be found here:
Application Object (Excel)