Changing Data in Columns in a CSV - powershell

I have a PowerShell script pulling data in from a CSV. What I am trying to do is "replace" the data in the Account column based on the value. For example, Account 001 = Hardware, Account 002 = Software, etc. The data in the CSV is being pulled from a SQL database so if it would be easier for me to change it in the SQL script, I can do that easily. The Account column in the CSV has 001, 002, etc. I want to change those values to Hardware, Software, etc. Thanks for the help.
$Results = import-csv Expenses.csv
$Array = #()
Foreach($R in $Results)
{
$Object = [pscustomobject][ordered] #{
Account = $R.Account
Vendor = $R.Desc1
Item = $R.Desc2
Amount = $R.Amount
}
$Array += $Object
}
$Array

If your CSV looks anything like this:
Account,Vendor,Item,Amount
001,Some Vendor,Something expensive, 1
002,Another Vendor,Something cheapish,26
you can update without a loop:
# create a lookup hashtable where you combine the account values with the wanted replacement
$lookup = #{
'001' = 'Hardware'
'002' = 'Software'
# etcetera
}
# import the csv and update the `Account` column
$Results = Import-Csv D:\Test\Expenses.csv | Select-Object #{Name = 'Account'; Expression = {$lookup[$_.Account]}}, * -ExcludeProperty Account
# display on screen
$Results
# output to (new) csv file
$Results | Export-Csv -Path D:\Test\Expenses_Updated.csv -NoTypeInformation
Result:
Account Vendor Item Amount
------- ------ ---- ------
Hardware Some Vendor Something expensive 1
Software Another Vendor Something cheapish 26
As per the comment of not2qubit some explanation about the Select-Object statement used.
Because the result should reflect all fields in the csv, where the existing field value named Account needs to be replaced, the code uses a Calculated property to set the Account field values using whatever was stored in the lookup Hashtable.
This is done with #{Name = 'Account'; Expression = {$lookup[$_.Account]}}
Next, all other fields contained in the csv are selected unchanged using the asteriks *.
Because we're overwriting the Accound field, but keep its name, the line ends with -ExcludeProperty Account in order to remove the original Account field in the output.
If we don't do that, PowerShell will show an error: Select-Object : The property cannot be processed because the property "Account" already exists.

If I have understood what you require correctly, you just want to change "001" to "Hardware" and so on in the object imported by the Import-Csv cmdlet. You can create a ScriptBlock with a switch that will return a value based off the value you have searched for. I could have recommended a Hashtable here too, but the benefit of a switch over a Hashtable, in this case, is that you can return the value using the default option if it is not specified. For example:
$Lookup = {
Param ([string]$Value)
switch ($Value) {
"001" { "Hardware" }
"002" { "Software" }
default { $Value }
}
}
$Results = Import-Csv Expenses.csv
foreach($R in $Results)
{
# Invoke the scriptblock with the named parameter.
$R.Account = & $Lookup -Value $R.Account
}
# Do stuff with $Results

Related

How to filter for specific words using power shell

I am trying to figure out who has been printing from a 2008 print server. I generated a log file from server manager and now have this information in a csv file. My goal is to parse through this info and export it to a new csv to find out the only the associates user ID, computer host name and printer name, all included in the csv log file so I can determine who is printing from this server and make sure I can map them to our new print server. The csv consists of one column of data which has a pattern.
Each row in the csv follows the below pattern, but the wording is different for each row since the job name is longer/shorter or other bits of information that I don't want.
The overall pattern is:
Document #, job name owned by **user ID** on **computer host name** was printed on **printer name** through port ********
More information I don't want
My problem is that I can't hard code something like ignore the first 5 words of each row then the 6th word would be the user ID etc, since the format is different on each row.
What is the best way for me to ignore all words up until either the phrase "owned by", or even better, user ID, save that to a new csv in, then do the same thing for the computer host name and printer name?
This could be done easily enough using Regular Expression matching. Regular Expressions use pattern matching, so you could do something like:
Get-Content LogFile.csv | Where{$_ -match "^(.*?),.+? owned by (.+?) on (.+?) was printed on (.+?) through port (.+)"} | ForEach{
[PSCustomObject]#{
JobNumber=$Matches[1]
UserId=$Matches[2]
ComputerName=$Matches[3]
PrinterName=$Matches[4]
Port=$Matches[5]
}
}|Export-Csv NewLogFile.csv -NoTypeInfo
That would give you a CSV you could open in Excel or something with just the job number, user ID, the computer they used, the printer it went to, and the port it went on.
TheMadTechnician's Answer already covers a majority of this.
$a = Get-Content original.csv
$a[0] += ",Data"
$a | Set-Content updated.csv
$csv = Import-Csv updated.csv
$data = $csv.where({$_."Event ID" -eq 307}) | Select-String -pattern "(?<=owned by )(?<user>[^ ]+)(?: on )(?<host>.*?)(?: was printed on )(?<printer>.*?)(?= through )"
$(ForEach ($m in $data.matches) {
[pscustomobject][ordered]#{"User"=$m.groups["user"].value
"Host"=$m.groups["host"].value
"Printer"=$m.groups["printer"].value
}
}) | Export-Csv Output.csv -notypeinformation
There are some issues with the CSV that is exported from the Windows print server. If the numbered issues below do not matter in this case, then I can just remove this answer.
The CSV column that contains the data you care about has no name. The other columns have headers, but for some reason this one does not. Without that header, your Import-Csv command will be useless. The first four lines of the code cover adding the Data header to that file. So you can either use code to fix that or just open the file, add the column name, and save.
The event ID you care about is 307. There's a lot of other noise in that event log unless you pre-filtered it before saving it as a CSV, and that could impact the regex matching.
My method here is really no different than the other posted answer. I'm just matching less strings and access those matches with named indexes.
This is not an answer for how to extract information from the message text but rather how to avoid having to deal with formatted text in the first place. It appears you are trying to parse the message for Event Log events with ID 307. This code is adapted from PowerShell One-Liner to Audit Print Jobs on a Windows based Print Server.
Using the Get-WinEvent cmdlet you can query a specific log (Microsoft-Windows-PrintService/Operational) for specific events (307), and then it's just a matter of retrieving and adding a meaningful name to each property...
$eventFilter = #{
LogName = 'Microsoft-Windows-PrintService/Operational';
ID = 307;
}
Get-WinEvent -FilterHashTable $eventFilter `
| Select-Object -Property `
'TimeCreated', `
#{ Name = 'JobId'; Expression = { $_.Properties[0].Value }; }, `
#{ Name = 'JobName'; Expression = { $_.Properties[1].Value }; }, `
#{ Name = 'UserName'; Expression = { $_.Properties[2].Value }; }, `
#{ Name = 'MachineName'; Expression = { $_.Properties[3].Value }; }, `
#{ Name = 'PrinterName'; Expression = { $_.Properties[4].Value }; }, `
#{ Name = 'PortName'; Expression = { $_.Properties[5].Value }; }, `
#{ Name = 'ByteCount'; Expression = { $_.Properties[6].Value }; }, `
#{ Name = 'PageCount'; Expression = { $_.Properties[7].Value }; }
For an event with a message like this...
Document 1, Print Document owned by UserName on \\MachineName was
printed on Microsoft Print to PDF through port X:\Directory\File.ext.
Size in bytes: 12345. Pages printed: 1. No user action is required.
...the above code will output an object like this...
TimeCreated : 3/28/2019 5:36:41 PM
JobId : 1
JobName : Print Document
UserName : UserName
MachineName : \\MachineName
PrinterName : Microsoft Print to PDF
PortName : X:\Directory\File.ext
ByteCount : 12345
PageCount : 1
You could pipe the above command into Export-CSV to create your CSV file, or even just use Out-GridView to view and filter the data directly in PowerShell. Either way, no parsing necessary.

How to to have an array as a return type on a custom PSObject

I've got the following:
# Compare the 2 lists and return the ones that exist in the top and children (meaning they're redundant).
$redundantUsers = $usersAssignedToThisGroup | where { $usersAssignedToGroupsInThisGroup -contains $_ }
# Build the results to output
$results += New-Object PSObject -property #{
group = $group.name #group assigned out of scope
users = #($redundantUsers)
}
I'm expecting to be able to call my script like:
$users = ./MyScript -myParam "something" | Select users
Then I'm expecting to be able to type $users[1] in the console and get the first element of that array.
But instead, I'm having to do $users[0].users[1].
How do I change my data and or call to do what I want?
I'm essentially just trying to let the script return the data in a useful way.

Modifying Column Within Array

I'm reading in a CSV file which contains 25,000 records, and am reading each column into a psobject. Here is what I have so far:
$file = Import-CSV .\server.csv
$tempobj = New-Object psobject -Property #{
'Name' = $file.Name
'Group' = $file.Group
}
When this is ran, I get the correct results I want, being that $file.Name contains all the server names, and $file.Group contains the groups for servers. However, my issue is that I need to edit the names of each server without interfering with the .Group. Here is an example of what a server name look like as is.
WindowsAuthServer #{wdk9870WIN}
I need to remove WindowsAuthServer #{ and WIN} from each server name, leaving only the server name left, or for this example, wdk9870.
I tried using the -replace function ($tempobj.Name -replace "WindowsAuthServer #{",""), but it requires that I save the results to a new array, which then messes up or removes .Group entirely
Is there a different way to go about doing this? I'm lost.
Suppose your server.csv looks like this:
"Name","Group"
"WindowsAuthServer #{wdk9870WIN}","Group1"
"WindowsAuthServer #{wdk9880WIN}","Group2"
"WindowsAuthServer #{wdk9890WIN}","Group1"
"WindowsAuthServer #{wdk9900WIN}","Group1"
And you want to change the values in the Name column only, then something like this would probably do it:
Import-Csv .\server.csv | ForEach-Object {
New-Object psobject -Property #{
'Name' = ([regex]'#\{(\w+)WIN\}').Match($_.Name).Groups[1].Value
'Group' = $_.Group
}
}
This will output:
Name Group
---- -----
wdk9870 Group1
wdk9880 Group2
wdk9890 Group1
wdk9900 Group1
If you want, you can simply pipe this info to the Export-Csv cmdlet to save as a new CSV file. For that, just append | Export-Csv -Path .\server_updated.csv -NoTypeInformation to the code.
Hope that helps

Export results of (2) cmdlets to separate columns in the same CSV

I'm new to PS, so your patience is appreciated.
I'm trying to grab data from (2) separate CSV files and then dump them into a new CSV with (2) columns. Doing this for (1) is easy, but I don't know how to do it for more.
This works perfectly:
Import-CSV C:\File1.csv | Select "Employee" | Export-CSV -Path D:\Result.csv -NoTypeInformation
If I add another Import-CSV, then it simply overwrites the existing data:
Import-CSV C:\File2.csv | Select "Department" | Export-CSV -Path D:\Result.csv -NoTypeInformation
How can I get columns A and B populated with the info result from these two commands? Thanks for your help.
I would have choose this option:
$1 = Import-Csv -Path "C:\Users\user\Desktop\1.csv" | Select "Employee"
$2 = Import-Csv -Path "C:\Users\user\Desktop\2.csv" | Select "Department"
$marged = [pscustomobject]#()
$object = [pscustomobject]
for ($i=0 ; $i -lt $1.Count ; $i++){
$object = [pscustomobject]#{
Employees = $1[$i].Employee
Department = $2[$i].Department}
$marged += $object
}
$marged | ForEach-Object{ [pscustomobject]$_ } | Export-Csv -Path "C:\Users\user\Desktop\3.csv" -NoTypeInformation -Force
I'll explain how I would do this, but I do it this way because I'm more comfortable working with objects than with hastables. Someone else may offer an answer using hashtables which would probably work better.
First, I would define an array to hold your data, which can later be exported to CSV:
$report = #()
Then, I would import your CSV to an object that can be iterated through:
$firstSet = Import-CSV .\File1.csv
Then I would iterate through this, importing each row into an object that has the two properties I want. In your case these are Employee and Department (potentially more which you can add easily).
foreach($row in $firstSet)
{
$employeeName = $row.Employee
$employee = [PSCustomObject]#{
Employee = $employee
Department = ""
}
$report += $employee
}
And, as you can see in the example above, add this object to your report.
Then, import the second CSV file into a second object to iterate through (for good form I would actually do this at the begining of the script, when you import your first one):
$secondSet = Import-CSV .\File2.csv
Now here is where it gets interesting. Based on just the information you have provided, I am assuming that all employees in the one file are in the same order as the departments in the other files. So for example, if I work for the "Cake Tasting Department", and my name is on row 12 of File 1, row 12 of File 2 says "Cake Tasting Department".
In this case it's fairly easy. You would just roll through both lists and update the report:
$i = 0
foreach($row in $secondSet)
{
$dept = $row.Department
$report[i].Department = $dept
$i++
}
After this, your $report object will contain all of your employees in one row and departments in the other. Then you can export it to CSV:
$report | Export-CSV .\Result.csv -NoTypeInformation
This works if, as I said, your data aligns across both files. If not, then you need to get a little fancier:
foreach($row in $secondSet)
{
$emp = $row.Employee
$dept = $row.Department
$report | Where {$_.Employee -eq $emp} foreach {$_.Department = $dept
}
Technically you could just do it this way anyway, but it depends on a lot of things. First of all whether you have the data to match in that column across both files (which obviously in my example you don't otherwise you wouldn't need to do this in the first place, but you could match across other fields you may have, like EmployeeID or DoB). Second, on the sovereignty of individual records (e.g., if you have multiple matching records in your first file, you will have a problem; you would expect duplicates in the second as there are more than one person in each department).
Anyway, I hope this helps. As I said there is probably a 'better' way to do this, but this is how I would do it.

Find and replace inside one csv column with powershell

I have a large CSV file that looks like this named student.export.text
Student Number,Last Name,Middle Name,First Name,Schoolid,Grade Level,Dob
I'm trying to build an automated task that will run nightly so that another piece of software can understand the CSV correctly.
Here is my code, but I'm missing something that is causing an error. I am new to Powershell and I am hoping for some advice.
Any help will be greatly appreciated!
$Replacements = #{
"5" = "AE";
"7" = "ER";
"10" = "FM";
"12" = "HC";
"14" = "JH";
"18" = "LE";
#...]
}
Import-Csv .\student.export.text | ForEach-Object {
$_.Schoolid = $Replacements[$_.Schoolid]
$_
} | Export-Csv -NoTypeInformation .\new.csv
Here's one approach that can work.
# declare hash table with School ID to School Name mapping
$schoolIdsToNames = #{
"3" = "SchoolA";
"4" = "SchoolB"
}
# import the CSV file
$csv = Import-Csv "C:\input.csv";
# for each row, replace the School ID field with the School Name
foreach($row in $csv)
{
$row.Schoolid = $schoolIdsToNames[$row.Schoolid];
}
# export the modified CSV
$csv | Export-Csv "C:\replaced.csv" -NoTypeInformation;
In the first step, we set up a PowerShell hashtable (a sort of key-value pair list), then import the CSV file using Import-Csv and store it in the $csv variable. This cmdlet will create an object from every row of the CSV that we can manipulate easily. For each row, we simply replace the Schoolid field with the value assigned to the ID key in the $schoolIdsToNames hashtable. Finally, we export the CSV to another file.
Another, more PowerShell-ly approach would be something like this:
Import-Csv "C:\test\school.csv" | Select-Object *, #{ Name = "SchoolName"; Expression = { $schoolIdsToNames[$_.Schoolid] } } | Export-Csv "C:\test\replaced2.csv" -NoTypeInformation
This one-liner imports the CSV and sends it down the pipeline. For each row, we select all properties of the row using Select-Object and add a new property called SchoolName, setting its value using the same hash table-based technique as above. Finally, we export the object list to CSV.