I have the following in a script:
$Loc = Read-Host "Please select a location number"
$ImpCSV = Import-Csv -Delimiter ";" -Path "file.csv" -Header loc,add | Where-Object {$_.loc -eq "$Loc"}
Foreach ($CIM in $ImpCSV) {$LocInfo = $CIM}
This seems to create some sort of hash/array/something variable for $Loc=1. If I enter:
$LocInfo.add
It gives me the value of that key(?) as it should in a hash table, but if I do:
$LocInfo
I get:
loc : [1]
add : [some value]
Which is not the usual hash output. This is what one should look like:
Name Value
---- -----
loc [1]
add [some value]
The issue this is first causing me is that if I try to edit an entry using $LocInfo.Set_Item("add", "HI"), it does nothing. Also, I created a table to display those variables creating new-objects, but the new -property command won't take $LocInfo.add, returning a "#{loc=1; add=some value}" if not giving me a syntax error. What I had to do was:
Set-Variable -Name add -Value $LocInfo.add
This sets add to [some value] and then I can put that in the new-object field without any issue.
This is additionally problematic because my script loops so they can add another location (loc) number (let's say 2) and pull up that location's data as necessary. If the new location they are searching has say the add (Address) missing, instead of creating a blank $LocInfo.add and then a blank $add, it skips it, meaning when the new-object is created again, it uses the $add from the previous run, [some value]; ie both loc 1 and 2 have the same $add value.
What that heck did I create, and how can I do anything with it? Is there a better way to pull variables from a CSV file that won't screw me up like this? Did I break Powershell? I've looked all over and I can't find this kind of variable arrangement.
This is the error I get when I try to do a Set_Item:
Method invocation failed because [System.Management.Automation.PSCustomObject] does not contain a method named 'Set_Item'.
At line:1 char:1
+ $LocInfo.Set_Item("add", "HI")
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (Set_Item:String) [], RuntimeException
+ FullyQualifiedErrorId : MethodNotFound
I've created objects using New-Object, and even those display like a hash table, ie with a header and multiple columns depending on the object size.
First thing: If you are ever not sure about the type of an object you can just check and object's type with GetType(). Import-Csv command returns a System.Object[] where a command like this #{Letter="Test"}.GetType().FullName would return System.Collections.Hashtable. .FullName returns the just the strings I am using in these examples.
Appears that you are trying to collect your output with the Where-Object using this line Foreach ($CIM in $ImpCSV) {$LocInfo = $CIM} The couple of issues I see with that are your keep overwriting $LocInfo with $CIM. At the end of the loop you will only have one variable. If you are trying to keep all of them would need to append each item and declare $LocInfo as an array.
$LocInfo = #()
....
Foreach ($CIM in $ImpCSV) {$LocInfo += $CIM}
But unless you have more code that you are not showing this appears redundant as you would have $LocInfo and $ImpCSV populated with the same data. You could just do away with the for loop altogether and just do this.
$Loc = Read-Host "Please select a location number"
$ImpCSV = Import-Csv -Delimiter ";" -Path "file.csv" -Header loc,add | Where-Object {$_.loc -eq "$Loc"}
I have changed nothing except remove the last line.
Does converting the result of import-csv into custom object like this help?
$ImpCSV = Import-Csv -Delimiter ";" -Path "file.csv" -Header loc,add | Where-Object {$_.loc -eq "$Loc"} | %{[pscustomobject]#{LOC=$_.LOC;ADD=$_.ADD}}
or
$ImpCSV = Import-Csv -Delimiter ";" -Path "file.csv" -Header loc,add | Where-Object {$_.loc -eq "$Loc"} | %{new-object -TypeName PSCustomObject -Property #{LOC=$_.LOC;ADD=$_.ADD}}
Import-Csv reads a CSV file and transforms it into a list of custom objects with the CSV columns as properties. However, objects with just 2 properties should be displayed in table format by default:
PS C:\> cat 'C:\test.csv'
1,foo
2,bar
3,baz
PS C:\> $csv = Import-Csv 'C:\test.csv' -Header loc,add | ? {$_.loc -eq '2'}
PS C:\> $csv
loc add
--- ---
2 bar
You should only get output in list format if you pipe the object(s) into the Format-List cmdlet:
PS C:\> $csv | Format-List
loc : 2
add : bar
or if the objects have 5 or more properties:
PS C:\> cat 'C:\test2.csv'
1,foo,a,a,a
2,bar,b,b,b
3,baz,c,c,c
PS C:\> Import-Csv 'c:\test2.csv' -Header loc,add,f3,f4 | ? {$_.loc -eq '2'}
loc add f3 f4
--- --- -- --
2 bar b b
PS C:\> Import-Csv 'c:\test2.csv' -Header loc,add,f3,f4,f5 | ? {$_.loc -eq '2'}
loc : 2
add : bar
f3 : b
f4 : b
f5 : b
You can enforce tabular output for objects that would normally be displayed in list format by piping them into the Format-Table cmdlet:
PS C:\> Import-Csv 'C:\test2.csv' -Header loc,add,f3,f4,f5 |
>> ? {$_.loc -eq '2'} | Format-Table
>>
loc add f3 f4 f5
--- --- -- -- --
2 bar b b b
Note, however, that formatting cmdlets like Format-List or Format-Table are intended for displaying data. Don't use them if you want to further process the object(s).
Related
I'm creating a PowerShell script for DPM which outputs offsite ready tapes. The below code takes a few seconds to complete the DPM connection and to return the query, which is fine.
# Connect to local DPM server
$DPMServer = $env:COMPUTERNAME
Connect-DPMServer -DPMServerName $DPMServer | Out-Null
# Get the DPM libary
$DPMLib = Get-DPMLibrary
#Format output
$Formatting = #{Expression={$_.Barcode}; Label="Barcode "; Width=12},
#{Expression={"{0:MM/dd/yyyy HH:mm tt}" -f $_.CreationDate}; Label="Creation Date "; Width=23},
#{Expression={$_.DisplayString}; Label="Tape Label "; Width=28},
#{Expression={"{0,15:N0}" -f $_.DataWrittenDisplayString}; Label="Data Written "}
#Calculate Monday at midnight of this week
$Monday = (Get-Date).AddDays((-1 * (Get-Date).DayOfWeek.Value__) + 1).Date
# Get specific tapes
$Tapes = Get-DPMTape -DPMLibrary $DPMLib |
Where-Object {$_.Barcode -notlike "CLN*"} |
Where-Object {$_.DisplayString -notlike "Free"} |
Where-Object {$_.CreationDate -gt $Monday} |
Sort-Object Barcode |
Format-Table -Property $Formatting
Write-Host "`nOffsite Ready Tapes:" -ForegroundColor Cyan
#Output tapes
$Tapes
This outputs like so:
My question is how do I now select just the barcodes listed within the $Tapes array, and output them (below what I already have) as a comma delimited list, without running the DPM query again. I've tried all sorts of things with no luck, I'm missing something obvious.
If I do a second connection to DPM, I can do it like this, but I'm trying to avoid doubling the time it takes to run. This is part of my original newbie script that I'm trying to better.
# Define DPM server to connect to
$DPMServer = $env:COMPUTERNAME
Connect-DPMServer -DPMServerName $DPMServer | Out-Null
# Get the DPM libary
$DPMLib = Get-DPMLibrary
# Get tape display strings and barcodes, sort by barcode
$Tapes = Get-DPMTape -DPMLibrary $DPMLib | Select-Object CreationDate, DisplayString, Barcode | Sort-Object Barcode
# Create empty array
$DPMTapesForOffsite = #()
Write-Host "`nOffsite Ready Tapes:`n" -ForegroundColor Cyan
foreach ($_ in $Tapes) {
# Exclude cleaning tapes
if ($_.Barcode -notlike "CLN*") {
# Exclude marked as free
if ($_.DisplayString -notlike "Free") {
$TimeStamp = Get-Date $_.CreationDate
# Timestamp is from this week
if ($Timestamp -gt $Monday) {
$DPMTapesForOffsite = $DPMTapesForOffsite + $_.barcode
Write-Host $_.barcode
}
}
}
}
# Format tape list as comma delimited
$DPMTapesForOffsite = $DPMTapesForOffsite -join ","
I'm missing some obvious, any help would greatly be appreciated.
# Omit Format-Table initially, so as to store actual *data* in $tapes,
# not *formatting instructions*, which is what Format-* cmdlets return.
$tapes = Get-DPMTape -DPMLibrary $DPMLib |
Where-Object {$_.Barcode -notlike "CLN*"} |
Where-Object {$_.DisplayString -notlike "Free"} |
Where-Object {$_.CreationDate -gt $Monday} |
Sort-Object Barcode
# Now you can apply the desired formatting.
Write-Host "`nOffsite Ready Tapes:" -ForegroundColor Cyan
$tapes | Format-Table -Property $Formatting
# Thanks to PowerShell's member-access enumeration feature,
# accessing property .Barcode on the entire $tapes *collection*
# conveniently returns its *elements'* property values.
# -join ',' joins them with commas.
$tapes.Barcode -join ','
Format-* cmdlets output objects whose sole purpose is to provide formatting instructions to PowerShell's output-formatting system - see this answer.
In short: only ever use Format-* cmdlets to format data for display, never for subsequent programmatic processing.
This answer explains member-access enumeration.
Im getting the names of these computers and putting them into an array. Now what i want to do is to convert them into a string array to be able to check which policy they are on using a Get-ADComputer for loop or using a foreach loop (Can you recommend which one to use)
$global:arrComputers = #()
$computerStrings = Get-ADComputer -Filter 'SamAccountName -like "*Name*"' | Select -Expand Name
foreach ($line in $computerStrings)
{
$a = $line.ToString()
$b = $a.split()
$temp = #{}
$temp = New-Object object
$temp | Add-Member -MemberType "noteproperty" -Name Name -Value $b[0]
$global:arrComputers += $temp
}
$global:arrComputers
This is the command i want to run to check the policy they are under
Get-ADComputer "Name" -Properties MemberOf | %{if ($_.MemberOf -like "*POLICY_NAME*") {Write-Host "ON"} else {Write-Host "NOT ON"}}
I have tested both blocks of code and they are working the only problem im having is turning that array into a string array. I also tried the ToString() To be able to loop through it with the Get-ADComputer "Name"
"memberOf" property in objects returned by "Get-ADComputer" returns a list of strings containing Distinguished Name of each group this computer is a member of.
Therefore, I assume when you say "This is the command i want to run to check the policy they are under", you are referring to a group membership that a group policy is targeting right?
Below code then will do it:
$computers = #();
Get-ADComputer -Filter * -Properties Name,MemberOf | %{if ($_.MemberOf -like "*computer_group_name*") { $computers += $_.Name } }
Explanation:
First line, define an array $computers
Second line, query AD for computer object properties Name,MemberOf
then, $_.MemberOf contains group name in string, add Name property(string) to array of strings you defined on line 1
I am trying to compare two arrays and the following code does not work:
$cluster="MyCluster"
foreach ($cluster in $clusters)
{$allnodes+=gwmi -q "Select Name from MSCluster_Node" -namespace root\mscluster - ComputerName $cluster -Authentication PacketPrivacy|Select-Object # {Name="Node";Expression={$_.Name}}
}}}}
foreach ( $cluster in $clusters)
{
$actnodes+=gwmi -class "MSCluster_Resource" -namespace "root\mscluster" -computername $cluster -Authentication PacketPrivacy | where {$_.type -eq "SQL Server"} | Select #{n='ServerInstance';e={("{0}\{1}" -f $_.PrivateProperties.VirtualServerName,$_.PrivateProperties.InstanceName).TrimEnd('\')}}, #{n='Node';e={$(gwmi -Authentication PacketPrivacy -ComputerName $cluster -Namespace "root\mscluster" -query "ASSOCIATORS OF {MSCluster_Resource.Name='$($_.Name)'} WHERE AssocClass = MSCluster_NodeToActiveResource" | Select -ExpandProperty Name)}}|Select "Node"
}
$passivenodes=Compare-Object -ReferenceObject($allnodes) -DifferenceObject($actnodes)
$passivenodes
The above code returns the wrong result.
But, if I change the data type to string like this:
$allnodes = [string[]]$allnodes|select -unique
$actnodes = [string[]]$actnodes|select -unique
then it works as desired.
But, in http://technet.microsoft.com/en-us/library/hh849941(v=wps.620).aspx it says
Inputs The input type is the type of the objects that you can pipe to
the cmdlet.
•System.Management.Automation.PSObject You can pipe a
DifferenceObject object to Compare-Object
Why can't I use compare without changing the data type? What is wrong?
While you can pass in an arbitrary array of objects, you probably should tell Compare-Object which properties of the object's you want to compare using the -Property parameter which can take the name of one or more properties to compare. Execute $allnodes | get-member to see which properties you want to compare.
I have a pipe-delimited file containing 5 columns. I need to append a sixth (pipe-delimited) column to the end of each row.
Old data:
a|b|c|d|e
p|q|r|s|t
New Data:
a|b|c|d|e|x
p|q|r|s|t|x
The sixth column (x) is a value which read from a text-file.
I am wondering if there is a quick way to append this data into existing data-file using powershell? The file contains variable number of rows (between 10 to 100,000)
Any help is appreciated
Simple text operations should work:
$replace = 'x'
(Get-Content file.txt) -replace '$',"|$replace"
a|b|c|d|e|x
p|q|r|s|t|x
For large files, you can do this:
$replace = 'x'
filter add-data {$_ -replace '$',"|$replace"}
Get-Content file.txt -ReadCount 1000 | add-data | add-content newfile.txt
That should produce very good performance with large files.
Assuming that your data does not have any headers in the CSV already, then you'll have to define the headers with the -Headers parameter of the Import-Csv cmdlet. To run the example below, put your data into a file called c:\test\test.csv. Then, run the script in PowerShell or PowerShell ISE.
# 1. Import the data
$Data = Import-Csv -Delimiter '|' -Path c:\test\test.csv -Header prop1,prop2,prop3,prop4,prop5;
# 2. Add a new member to each row
foreach ($Item in $Data) {
Add-Member -InputObject $Item -MemberType NoteProperty -Name prop6 -Value x;
}
# 3. Export the data to a new CSV file
$Data | Export-Csv -Delimiter '|' -Path c:\test\test.new.csv -NoTypeInformation;
# 4. Remove the double quotes around values
(Get-Content -Path c:\test\test.new.csv -Raw) -replace '"','' | Set-Content -Path c:\test\test.new.csv;
Original Data
The source data in c:\test\test.csv should look like this (according to your original post):
a|b|c|d|e
p|q|r|s|t
Resulting Data
After executing the script, your resulting data in c:\test\test.new.csv will look like this:
prop1|prop2|prop3|prop4|prop5|prop6
a|b|c|d|e|x
p|q|r|s|t|x
Random Sample Data Generation
Here is a short script that will generate a 10,000-line, randomized sample file to c:\test\test.csv:
$Random = { [System.Text.ASCIIEncoding]::ASCII.GetString((1..5 | % { [byte](Get-Random -Minimum 97 -Maximum 122); })).ToCharArray(); };
1..10000 | % { #('{0}|{1}|{2}|{3}|{4}' -f (& $Random)) } | Set-Content -Path c:\test\test.csv;
After running my first script against this sample data (10,000 lines), the result took: 1,729 milliseconds to execute. I would say that's pretty fast. Not that this is a race or anything.
I ran the sample file generator again, to generate 100,000 lines of data. After running the same script against that data, it took 19,784 milliseconds to run. It's roughly proportional to the 10,000 line test, but all in all, still doesn't take all that long. Is this a one-time thing, or does it need to be run on a schedule?
You could loop through the file line for line and just append the value in the loop:
Edit full sample code:
function append{
process{
foreach-object {$_ + "|x"}}}
$a = get-content yourcsv.csv
$a | append | set-content yourcsv.csv
I have two csv files:
ipaddress,port
10.140.11.1,80
10.140.11.2,80
ipaddress,port
10.140.11.1,80
10.140.11.2,8008
The question is how to compare the files in powershell. I have already tried this:
$file1 = import-csv "csvfile1.csv"
$file2 = import-csv "csvfile2.csv"
Compare-Object $file1 $file2 -IncludeEqual
The result is as those two files were equal.
It works as expected if I specify the particular property, e.g:
Compare-Object $file1 $file2 -IncludeEqual -Property port
How to compare the csv files without specifying the properties. Let's say I would like to compare all properties in the csv file.
You can obtain the list of CSV column properties via Get-Member -MemberType NoteProperty, then pass that list to Compare-Object.
# get list of CSV properties
$props1 = $file1 | gm -MemberType NoteProperty | select -expand Name | sort | % {"$_"}
$props2 = $file2 | gm -MemberType NoteProperty | select -expand Name | sort | % {"$_"}
# first check that properties match (can omit this step if you know for sure they will be)
if(Compare-Object $props1 $props2)
{
throw "Properties are not the same! [$props1] [$props2]"
}
# pass properties list to Compare-Object
else
{
Compare-Object $file1 $file2 -Property $props1
}
The answer by latkin will not work.
You will get the following exception:
Compare-Object : Cannot convert System.Management.Automation.PSObject to one of the following types {System.String, System.Management.Automation.ScriptBlock}.
At line:8 char:19
+ Compare-Object <<<< $file1 $file2 -Property $props1
+ CategoryInfo : InvalidArgument: (:) [Compare-Object], NotSupportedException
+ FullyQualifiedErrorId : DictionaryKeyUnknownType,Microsoft.PowerShell.Commands.CompareObjectCommand
It seems that one cannot pass a variable for -Property. It has to be a comma-seperated list of NoteProperties and it cannot be enclosed in single or double quotes.
I've been looking for a way to do this same thing and I still haven't found a way...