How do I match a value in multiple rows in CSV - powershell

I have a csv file that contains
Full Name
Some Column
Access Required
John Smith
Stuff
System 1
John Smith
something
System 3
Bob Villa
other
Application 4
Bob Villa
more
Application 6
Doug Mack
stuff
System 2
I'd like to import this csv and match based upon one "full name" at a time (that I'm getting from a variable ie:Full Name) so that I get an out which I'll send as the body of an email that is:
Full Name: John Smith
Access Required: System 1
Access Required: System 3
Then I will run script again looking for Bob Villa and get:
Full Name: Bob Villa
Access Required: Application 4
Access Required: Application 6
I think I could do this with a bunch of "if statements" but I'm wondering if there's a way to parse the csv based upon "full name" and then list all the entries found in the "Access Required" column for each row that "full name" is found?
Note: I don't want anything from any other columns (and there could be lots of them), just want the contents of "Access Required"
Thank you!

Import the csv in a variable and select the data for the user by the full name:
$data = Import-Csv -Path 'path\to\input.csv'
# example select John Smith
$user = 'John Smith'
$selection = $data | Where-Object {$_.'Full Name' -eq $user}
if (#($selection).Count) {
# format as you like, for simplicity
$table = $selection | Format-Table -AutoSize | Out-String
# in a html mail embed $table inside '<pre>' tags, so
# it will display in a monospace font.
# $body = "<pre>$table</pre>"
}
else {
Write-Warning "Could not find $user"
}
Just import once, keep variable $data and repeat the part that starts with $user = ... for each new user

I was able to solve the issue using a combination of Foreach, an if statment and then switch.
$source = '\somesoure.csv'
$csv = import-csv $source
Foreach($row in $csv){
$personName = $row."Full Name"
$accessRequired = $row."Access Required"
if ($personName -eq $mayInputNameVariable){
//do some code stuff such as write to a column
switch ($accessRequired){
"System 1" {
$System1 = "Access Required: System 1`n"
}
//do it for all switch values
}
$emailBody = "Full Name:$personName`n" + $system1 + $system2 ...etc
$csv | Export-CSV $source -NoTypeInformation
Thanks folks!

Related

Powershell: How to import csv find a value in a row, list of the column data and over-write some coumns?

I think it's easiest to show what I have and what I'm trying to get as an output instead of me trying to verbalize what I'm doing and what I've tried.
Input is a csv that contains:
Full Name
Color
Access Type
Access ID
John Smith
Blue
Full_Acccess
jsmith
John Smith
Blue
Partial_Access
John Smith
Red
No_Access
Bill Bob
Red
Full_Access
Bill Bob
Pink
Access_1
Lisa Smith
Green
Access_2
I will call this script each time and look for "Full Name". Then if "Access ID" is empty, I want to populate Access ID (from a variable) and write out the "Output" below to a variable and send that as the body of an email.
So run script, look for "Full Name". In this case, it will look for and finds "Bill Bob" and since Access ID is empty, it will write "Rob Bob" (a variable value) to "Access ID" but also send email body of output below
OutPut:
Full Name: Bill Bob
Color: Red, Pink
Date: 03-21-2021
Access_Type: Full_Access, Access_1
Access ID: Rob Bob
So it's showing a unique Full Name (not all three rows), but also all values of Color and Access Type for "Full Name"
I've got this working using:
Import-CSV |Group-Object -Propert 'Full Name' | Select-Object #{Name = "Full Name"; Expression = {(_.Group.'Full Name' | Select-Object -Unique)}},
#{Name = "Access Type"; Expression = {($_.Group.'Access Type' | Select-Object -Unique) -join ","}},
#{Name = "Color"; Expression = {($_.Group.'Color' | Select-Object -Unique)}},
#{Name = "New Name"; Expression = {($_.Group.'New Name' | Select-Object -Unique)}} |
Where-Object {$_.'Full Name' -eq $PersonName}
Where I'm stuck:
Script will be called and imports CSV and searches for "Bill Bob". Then I want to list all the columns for Bill AND write the new Access_ID value back into the CSV.
Then I call the script again and look for John Smith, and then again for Lisa Smith, etc..
Thanks all!
I think I got what you were going for, here's what I would use:
Function Get-UserAccess ([string]$fullname,[string]$csvpath){
# Export your output
$csv = Import-CSV $csvpath
$filtered = $csv | Where-Object {$_.'Full Name' -eq $fullname}
$output = [pscustomobject]#{
'Full Name' = $fullname
'Access Type'=($filtered.'Access Type' | Select -Unique) -join ','
'Color' =($filtered.'Color' | Select -Unique) -join ','
'Access ID' =($filtered.'Access ID' | Select -Unique) -join ','
}
# check if Access ID is empty on Full_Access users
if (-not $output.'Access ID' -and
($output.'Access Type' -split ',') -contains 'Full_Access') {
# Set your AccessID here
$AccessID = Read-Host "Enter a new access ID for $fullname"
# Update the Access ID in the csv:
$entry = ([Collections.Generic.List[Object]]$csv).findIndex({
$args[0].'Full Name' -eq $fullname -and
$args[0].'Access Type'-eq 'Full_Access'})
$csv[$entry].'Access ID' = $AccessID
Write-Host "Updating $csvpath with new access ID for $fullname"
$csv | Export-Csv $csvpath -Force -NoTypeInformation
# Return output with Access ID
$output.'Access ID' = $AccessID
return $output
} Else { Return $output }
}
And some example usage with the data from your question:
PS C:\> Get-UserAccess 'Bill Bob' 'C:\temp\temp.csv' | fl
Enter a new access ID for Bill Bob: Rob Bob
Updating C:\temp\temp.csv with new access ID for Bill Bob
Full Name : Bill Bob
Access Type : Full_Access,Access_1
Color : Red,Pink
Access ID : Rob Bob
PS C:\> Get-UserAccess 'John Smith' 'C:\temp\temp.csv' | fl
Full Name : John Smith
Access Type : Full_Access,Partial_Access,No_Access
Color : Blue,Red
Access ID : jsmith,

Powershell: How to read rows from CSV and if a column contains a string set variable(s) to each header of column that string is found in

Let me try this again :)
What I have is a csv file that has a name and logonname column and then several other columns. To make it easier I've called them colour names. Each colour name may contain a "Yes" in that cell.
I have a powershell script that will only act upon one row at a time (a vendor tool). So it will read the value of Name and create an account, I write the logon name (samAccountName) into LogonName column beside that user.
This part works of the script works fine and uses import-csv and a Foreach($row in $csv) and writes logon name via $row.logonname
What I'm looking for is to add to my script so that it looks for "Yes" in any and all columns on the row of that user(Name) and if it finds a "Yes" then I'm sending an email with the a body containing Name LogonName and then headername: Yes Which should look like this:
Name:John Smith
LogonName:jsmith
Blue:Yes
Green:Yes
I have an input csv file "test.csv" that contains:
Name
LogonName
Blue
Red
Green
John Smith
Yes
Yes
My working section is:
$csv = import-Csv $testFile
Foreach($row in $csv){
$PersonName = $row.name
if ($PersonName -eq $($nameVariable)){
$row.LogonName = $($userVariable)
$row.Password = $($passwordVariable)
}
}
$csv | Export-CSV $testFile -NoTypeInformation
Sorry about all the confusion and poorly worded versions.
Thank you!
Here is a hint on how you can approach your script. Note that I'm using ConvertFrom-Csv instead of Import-Csv but it will work the same if you're reading a CSV and piping directly to ForEach-Object.
#'
Name,Blue,Red,Green
John,Yes,,Yes
Sam,,Yes,,
'# | ConvertFrom-Csv | ForEach-Object{
$_.PSObject.Properties.Where({$_.Value}).foreach({
"{0}: {1}" -f $_.Name, $_.Value
})|Out-String
}
Output looks like this:
Name: John
Blue: Yes
Green: Yes
Name: Sam
Red: Yes
Now, its a bit hard to tell how exactly you wish the output too look. If you give us more details we may be able to help you.
Check each of the "columns" by name in a nested loop:
Import-Csv C:\scripts\test.csv |ForEach-Object {
# Write the name (eg "John")
Write-Host $_.Name
foreach($color in 'Blue','Red','Green'){
# For each color name, check if the corresponding column says "yes"
if($_.$color -eq 'yes'){
Write-Host "${color} = yes"
}
}
}

Output logic for all collected data in powershell

I have a PS script I've created to connect to an XML sheet and collect the nodes I need, makes modifications/conversions and then I'm attempting to put it into one list using $books | Format-Table. However this ends up putting the header over each entry, when I output to a textile I had it do a newline and append, is this possible using the table function or is it something I would need to call outside the foreach loop? Im new to programming in general so I'm sure I'm just missing something.
I'm ending up with:
Column 1
========
123456
Column 1
========
123456
But looking for:
Column 1
========
123456
123456
cls
$storenum = Read-Host -Prompt 'Store Number'
$data = Read-Host -Prompt 'SKUS'
$foos = ($data | select-string -pattern '\d{4}-\d{3}-\d{3}|\d{10}' -AllMatches).Matches.Value
#|\d{10}
foreach ($foo in $foos) {
[xml]$books = (Invoke-webrequest -URI "http://st${storenum}.server/info?sku=${foo}").Content
#Meet conditions for matches, SKU is set
if ($books.ItemInfoResponse.results.skuNumber -gt "0")
{
$books | Format-Table -AutoSize -Property SKU,
#{Label="SKU"; Expression={$books.ItemInfoResponse.results.skuNumber}},
#{Label="Description"; Expression={$books.ItemInfoResponse.results.skuDescription}}
}}
Only use format-table once or not at all.

[PowerShell]Get-Content and Add Column Entry?

I am trying to input a list of users into PowerShell and get a specific security group attached to the user's account. At this current time, I have two pieces - an Excel sheet with multiple pieces of data, and a .txt with just the user's usernames. The script I have currently just inputs the user's usernames from the .txt and gets the security group from their account that matches a specific prefix, however I noticed doing it this way doesn't give any specific order. Even though the users are in a specific order (copied and pasted exactly from the excel document), the actual output doesn't come back well.
So, here's what I'd Like to do now, I just don't know how. I would like to get the content from the Excel document, take all of the usernames and do Get-ADPrincipalGroupMembership like I am now, and then write the security group Back to the line that matches the username. For example, if I looked up the SG for msnow, it would get the SG for msnow and then write the SG back to the row that has msnow, and continues through the list. Instead of just doing an Out-GridView, it would actually write this to the Excel document.
Any help on making this work?
Here is the code I have right now.
Import-Module ActiveDirectory
$Names = Get-Content C:\Temp\Users.txt
$Records = #()
Foreach ($ADUsers in $Names) {
Try {
$SG = Get-ADPrincipalGroupMembership -Identity $ADUsers | Select Name | Where {$_.Name -Like "SG - *"}
$SGName = $SG.Name
}
Catch [ADIdentityNotFoundException] {
$SGName = "User not found"
}
$Records += New-Object PSObject -Property #{"UserName" = $ADUsers;"Security Group" = $SGName}
}
Write-Host "Generating CSV File..."
$Records | Out-GridView
Thank you!
If you save the Excel as CSV, so it will look something like
"UserName","Security Group","InsideInfo"
"bloggsj","","tall guy"
"ftastic","","nothing worth mentioning"
things shouldn't be that hard to do.
$out = 'D:\Test\Updated_usersandgroups.csv'
$csv = Import-Csv -Path 'D:\Test\usersandgroups.csv'
Write-Host "Updating CSV File..."
foreach ($user in $csv) {
try {
$SG = Get-ADPrincipalGroupMembership -Identity $user.UserName -ErrorAction Stop
# if more groups are returned, combine them into a delimited string
# I'm using ', ' here, but you can change that to something else of course
$SGName = ($SG | Where-Object {$_.Name -Like "SG - *"}).Name -join ', '
}
catch [ADIdentityNotFoundException] {
$SGName = "User $($user.UserName) not found"
}
catch {
# something else went wrong?
$SGName = $_.Exception.Message
}
# update the 'Security Group' value
$user.'Security Group' = $SGName
}
Write-Host "Generating updated CSV File..."
$csv | Export-Csv -Path $out -UseCulture -NoTypeInformation
# show output on screen
$csv | Format-Table -AutoSize # or -Wrap if there is a lot of data
# show as GridView (sorts by column)
$csv | Out-GridView
Output in console would then look like
UserName Security Group InsideInfo
-------- -------------- ----------
bloggsj SG - Group1, SG - Group1 tall guy
ftastic SG - Group1 nothing worth mentioning
Note: I don't know what delimiter your Excel uses when saving to CSV file. On my Dutch machine, it uses the semi-colon ;, so if in your case this is not a comma, add the delimiter character as parameter to the Import-Csv cmdlet: -Delimiter ';'
Excel uses whatever is set in your locale as ListSeparator for the delimiter character. In PowerShell you can see what that is by doing (Get-Culture).TextInfo.ListSeparator. On output, the -UseCulture switch will make sure it uses that delimiter so Excel will understand

Powershell - Match one or another

I have a log file that has user login/logout events, formatted like this:
11/29/19 10:46:41.976 S I [T-22156] - User Logout: Bob Jones (Home)
11/29/19 10:46:51.293 S I [T-22156] - HTTP UserMgr: Start User login notify for user: Jane Smith (Studio)
I am calling the above text from my log file using Get-Content and saving it to $testtext.
In Expresso, I built the following regex, and when I run it, Expresso matches my DateTime, Status, and Name without any errors on the above text.
$regex = [regex]::new('(?<DateTime>\d{2}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2})(?:.*)(?<Status>User\slog\w{2,3})(?:.*?)(?<Name>[A-Z][a-zA-Z]+\s[A-Z][a-zA-Z]+\s\([A-Z]+\))')
But when I run the following in Powershell, it only outputs the Login events
$parsed = foreach ($text in $testtext) {
$match = $regex.Match($text)
[PSCustomObject][ordered]#{
Date = $match.Groups["DateTime"].Value
Status = $match.Groups["Status"].Value
Name = $match.Groups["Name"].Value
}
}
$parsed | Out-GridView
How can I have my regex look at each line and fine both matches?
the following is what i meant by "two steps instead of one massive regex". [grin]
what it does ...
fakes reading in text file
replace that entire section with a Get-Content call to load your text file as an array of lines of text.
iterates thru the resulting collection of lines
tests for either login or logout
applies a different regex for each type of line
making a regex for both lines is both more complex and somewhat beyond my skill level. [grin]
creates a PSCO for each set of matches
sends the PSCO out to the $Results collection
displays that collection
at that point, you can export to CSV fairly simply.
the code ...
# fake reading in a plain text file
# in real life, use Get-Content
$InStuff = #'
11/29/19 10:46:41.976 S I [T-22156] - User Logout: Bob Jones (Home)
11/29/19 10:46:51.293 S I [T-22156] - HTTP UserMgr: Start User login notify for user: Jane Smith (Studio)
'# -split [System.Environment]::NewLine
$Results = foreach ($IS_Item in $InStuff)
{
if ($IS_Item -match 'login')
{
$Null = $IS_Item -match '^(.*\s.*) S I .* user (.*) notify for user: (.*)$'
[PSCustomObject]#{
TimeStamp = $Matches[1]
Type = $Matches[2]
User = $Matches[3]
}
}
if ($IS_Item -match 'logout')
{
$Null = $IS_Item -match '^(.*\s.*) S .* User (.*): (.*)$'
[PSCustomObject]#{
TimeStamp = $Matches[1]
Type = $Matches[2]
User = $Matches[3]
}
}
}
$Results
output ...
TimeStamp Type User
--------- ---- ----
11/29/19 10:46:41.976 Logout Bob Jones (Home)
11/29/19 10:46:51.293 login Jane Smith (Studio)
I maybe understanding it wrong. But it looks like the contents are only 1 object. Then when you do a foreach it only checks the one object.
What I would check...
What is the value of $testtext.count? You want it to match the number of lines, not be 1.
If it is 1, then you need to look at splitting each line to be treated as an object.