For each thing in one CSV check for multiple types of matches in another CSV - powershell

Sorry if the description is unclear, but I couldn't think of how else to word it.
I have two CSV files:
LocalAdmins.csv -- ColumnA = PC name; ColumnB = username in local admin group
Exempt.csv -- ColumnA = PC name; ColumnB = username allowed to be a local admin
What I'm trying to do is loop through LocalAdmins.csv, and for each one check to see if the PC name shows up in Exempt.csv (or matches any defined naming patterns in that file), and if a match is found, check to see if the local admin username for that PC in LocalAdmins.csv shows up in the list of AllowedUsers for that PC in Exempt.csv.
If the username is NOT in the AllowedUsers list, or if the PC name is not in Exempt.csv, then output the entry from LocalAdmins.csv. Here is what I have so far:
$admins = Import-Csv .\LocalAdmins.csv
$exempt = Import-Csv .\Exempt.csv
$violations = ".\Violations.csv"
foreach ($admin in $admins) {
foreach ($item in $exempt) {
if ($admin.PC -like $item.PC) {
if ($admin.Name -notin ($item.AllowedUsers -split ",")) {
$admin | Export-Csv $violations -Append -NoTypeInformation
}
}
else {
$admin | Export-Csv $violations -Append -NoTypeInformation
}
}
}
The problem is the nested foreach loop generates duplicates, meaning if there are 3 lines in Exempt.csv then a single entry in LocalAdmins.csv will have 3 duplicate outputs (one for each line in Exempt.csv). So the output looks like this:
When it should look like this:
I'm guessing the problem is somewhere in the structure of the loops, but I just need some help figuring out what to tweak. Any input is greatly appreciated!

Not optimized (unique sort by any property should work):
$admins = Import-Csv .\LocalAdmins.csv
$exempt = Import-Csv .\Exempt.csv
$violations = ".\Violations.csv"
$(
foreach ($admin in $admins) {
foreach ($item in $exempt) {
if ($admin.PC -like $item.PC) {
if ($admin.Name -notin ($item.AllowedUsers -split ",")) {
$admin
}
}
else {
$admin
}
}
}
) | Sort-Object -Property PC, Name -Unique |
Export-Csv $violations -Append -NoTypeInformation

With better restrictions of the forEach, there shouldn't be duplicates
and no need to Sort -unique.
Getting input from here-strings
## Q:\Test\2019\02\05\SO_54523868.ps1
$admins = #'
PC,NAME
XYZlaptop,user6
workstationXYZ,user7
computerABC,user8
ABClaptop,user1
'# | ConvertFrom-Csv # .\LocalAdmins.csv
$exempt = #'
PC,AllowedUsers
*laptop,"user1,user2"
computerXYZ,"user3,user4"
workstation*,"user5"
'# | ConvertFrom-Csv # .\Exempt.csv
$violationsFile = ".\Violations.csv"
$violations = foreach ($admin in $admins) {
$violation = $True
foreach ($item in ($exempt|Where-Object {$admin.PC -like $_.PC})){
if ($admin.NAME -in ($item.AllowedUsers -split ',')){
$violation = $False
}
}
if ($violation){$admin}
}
$violations
$violations | Export-Csv $violationsFile -NotypeInformation
## with Doug Finke's ImportExcel module installed, you can directly get the excel file:
#$violations | Export-Excel .\Violatons.xlsx -AutoSize -Show

Related

Trying to extract specific text and merge output with existing output

I want to extract text from a .txt file. The way the file is layed out is in this format (below first block). Optimally, I would like for the powershell script to take the content of username and votecount and output them side by side. With an integer of 25>= add the letter D beside it. With the output adding itself to a pre-existing output file. Say this week is week 1. And testuser voted 25 times. They should have the output "testuser" 25D. But say in week 2 they voted 24 times. Then it should be "testuser" 49D. However say they had 25 again. Output should then be "testuser" 50DD or 50D2?.. I have what I think should work as an initial baseline for the script which in itself doesn't work.. But combining an output with a pre existing output is beyond my capability. This needs to parse an entire txt file of some 100+ people. So imagine there's like an extra 100 users..
{
"username": "testuser",
"votecount": "42",
"votesclaimed": "0",
"lastvotetime": "2022-11-04 09:08:29",
"steamid": "00000000000000000000"
}
Below is what I am working with.
Get-Content -Raw C:\Users\--------\Desktop\votes.txt |
ConvertFrom-txt |
ForEach-Object {
[pscustomobject] #{
UserName = $_.username
VoteCount = '{0}{1}' -f $_.votecount, ('', 'D')[[int] $_.votecount -gt 25]
}
} |
Export-Csv -NoTypeInformation -Encoding utf8 C:\Users\---------\Desktop\outvotes.csv
Try following :
$match = Select-String -Path "c:\temp\test.txt" -Pattern '^\s*"(?<key>[^"]+)"\s*:\s*"(?<value>[^"]+)'
$table = [System.Collections.ArrayList]::new()
foreach( $row in $match.Matches )
{
$key = $row.Groups["key"].Value
$value = $row.Groups["value"].Value
if($key -eq "username") {
$newRow = New-Object -TypeName psobject
$table.Add($newRow) | Out-Null
}
$newRow | Add-Member -NotePropertyName $key -NotePropertyValue $value
}
$table | Format-Table
$groups = $table | Group-Object {$_.username}

Powershell Cascading Foreach to export to Csv

I'm exporting my AAD users to CSV files, works fine with this code.
$allUsers = Get-AzureADUser -All $true
$users = $allUsers |
Select-Object -Property ObjectId,ObjectType,UserPrincipalName,DisplayName,AccountEnabled,AgeGroup,City,CompanyName,ConsentProvidedForMinor,Country,CreationType,Department,DirSyncEnabled,FacsimileTelephoneNumber,GivenName,IsCompromised,ImmutableId,JobTitle,LastDirSyncTime,LegalAgeGroupClassification,Mail,MailNickName,Mobile,OnPremisesSecurityIdentifier,PasswordPolicies,PhysicalDeliveryOfficeName,PostalCode,PreferredLanguage,RefreshTokensValidFromDateTime,ShowInAddressList,State,StreetAddress,Surname,TelephoneNumber,UsageLocation,UserState,UserStateChangedOn,UserType,DeletionTimestamp,AssignedLicenses,AssignedPlans,ProvisionedPlans |
ForEach-Object{
$_.DisplayName = $_.DisplayName -replace "\n", ' ' -replace '"', '`'
$_
}
$users | Export-Csv -Path $TempFileName -NoTypeInformation
$provisionedPlans = $users = $allUsers |
Select-Object -Property ObjectId,DisplayName,ProvisionedPlans
But, ProvisionedPlans comes out as a list, so I would like to export it for each entry in the list as 1 line.
This is a sample of the field
ProvisionedPlans : {class ProvisionedPlan {
CapabilityStatus: Enabled
ProvisioningStatus: Success
Service: MicrosoftCommunicationsOnline
}
, class ProvisionedPlan {
CapabilityStatus: Deleted
ProvisioningStatus: Success
Service: MicrosoftCommunicationsOnline
}
, class ProvisionedPlan {
CapabilityStatus: Deleted
ProvisioningStatus: Success
Service: MicrosoftCommunicationsOnline
}
, class ProvisionedPlan {
CapabilityStatus: Enabled
ProvisioningStatus: Success
Service: SharePoint
}
...}
So bottom line what I would like to see in the output would be
ObjectId,DisplayName,CapabilityStatus,ProvisioningStatus,Service
id1,User1,Enabled,Success,MicrosoftCommunicationsOnline
id1,User1,Deleted,Success,MicrosoftCommunicationsOnline
id1,User1,Deleted,Success,MicrosoftCommunicationsOnline
id1,User1,Enabled,Success,SharePoint
id2,User2,Enabled,Success,Whatever
So please feel free, i'm not a Powershell specialist.
Following your guidance of what you want as a bottom line please see the below adjustments to your script:
$allUsers = Get-AzureADUser -All $true
$Results = Foreach ($user in $allusers){
Foreach($Plan in $user.ProvisionedPlans){
$Plan | Select #{Name = 'ObjectId'; Expression = {$user.Objectid}},#{Name = 'DisplayName';Expression = {$user.DisplayName}},CapabilityStatus,ProvisioningStatus,Service
}
}
$Results | Export-Csv -Path $TempFileName -NoTypeInformation
I am not sure why you are replacing characters in the DisplayName but I have added this to the calculated properties in the Select Statemant.
I have also done this on the iteration of each provisioned plan as this seemed to be the easiest route.
I have removed the initial select with all of the properties as the properties you are interested in are being exported. (If you require all properties I would advise using Select * as it will pull the majority of properties in most cases and will look tidier in the code.)

Combining like objects in an array

I am attempting to analyze a group of text files (MSFTP logs) and do counts of IP addresses that have submitted bad credentials. I think I have it worked out except I don't think that the array is passing to/from the function correctly. As a result, I get duplicate entries if the same IP appears in multiple log files. What am I doing wrong?
Function LogBadAttempt($FTPLog,$BadPassesArray)
{
$BadPassEx="PASS - 530"
Foreach($Line in $FTPLog)
{
if ($Line -match $BadPassEx)
{
$IP=($Line.Split(' '))[1]
if($BadPassesArray.IP -contains $IP)
{
$CurrentIP=$BadPassesArray | Where-Object {$_.IP -like $IP}
[int]$CurrentCount=$CurrentIP.Count
$CurrentCount++
$CurrentIP.Count=$CurrentCount
}else{
$info=#{"IP"=$IP;"Count"='1'}
$BadPass=New-Object -TypeName PSObject -Property $info
$BadPassesArray += $BadPass
}
}
}
return $BadPassesArray
}
$BadPassesArray=#()
$FTPLogs = Get-Childitem \\ftpserver\MSFTPSVC1\test
$Result = ForEach ($LogFile in $FTPLogs)
{
$FTPLog=Get-Content ($LogFile.fullname)
LogBadAttempt $FTPLog
}
$Result | Export-csv C:\Temp\test.csv -NoTypeInformation
The result looks like...
Count IP
7 209.59.17.20
20 209.240.83.135
18441 209.59.17.20
13059 200.29.3.98
and would like it to combine the entries for 209.59.17.20
You're making this way too complicated. Process the files in a pipeline and use a hashtable to count the occurrences of each IP address:
$BadPasswords = #{}
Get-ChildItem '\\ftpserver\MSFTPSVC1\test' | Get-Content | ? {
$_ -like '*PASS - 530*'
} | % {
$ip = ($_ -split ' ')[1]
$BadPasswords[$ip]++
}
$BadPasswords.GetEnumerator() |
select #{n='IP';e={$_.Name}}, #{n='Count';e={$_.Value}} |
Export-Csv 'C:\Temp\test.csv' -NoType

Powershell : merge two CSV files with partially duplicate lines

I have scraped two files from a website in order to list the companies in my city.
The first lists : name, city, phone number, email
The second lists : name, city, phone number
And I will have duplicate lines if I merge them, as an example, i will have the following :
> "Firm1";"Los Angeles";"000000";"info#firm1.lol"
> "Firm1";"Los Angeles";"000000";""
> "Firm2";"Los Angeles";"111111";""
> "Firm3";"Los Angeles";"000000";"contact#firm3.lol"
> "Firm3";"Los Angeles";"000000";""
> ...
Is there a way to merge the two files and keep the max info like this :
> "Firm1";"Los Angeles";"000000";"info#firm1.lol"
> "Firm2";"Los Angeles";"111111";""
> "Firm3";"Los Angeles";"000000";"contact#firm3.lol"
> ...
According to the fact you've got a file like this called 'firm.csv'
"Firm1";"Los Angeles";"000000";"info#firm1.lol"
"Firm1";"Los Angeles";"000000";""
"Firm2";"Los Angeles";"111111";""
"Firm3";"Los Angeles";"000000";"contact#firm3.lol"
"Firm3";"Los Angeles";"000000";""
You can load it using :
$firms = import-csv C:\temp\firm.csv -Header 'Firm','Town','Tel','Mail' -Delimiter ';'
Then
$firms | Sort-Object -Unique -Property 'Firm'
According to Joey's comment I improved the solution :
$firms | Group-Object -Property 'firm' | % {$_.group | Sort-Object -Property mail -Descending | Select-Object -first 1}
EDIT: just realized the two files don't contain the same headers. Here is an update.
$main = Import-Csv firm1.csv -Header 'Firm','Town','Tel','Mail' -Delimiter ";"
$alt = Import-Csv firm2.csv -Header 'Firm','Town','Tel' -Delimiter ";"
foreach ($f in $alt)
{
$found = $false
foreach($g in $main)
{
if ($g.Firm -eq $f.Firm -and $g.city -eq $f.city)
{
$found = $true
if ($g.Tel -eq "")
{
$g.Tel = $f.Tel
}
}
}
if ($found -eq $false)
{
$main += $f
}
}
# Everything is merged into the $main array
$main
There must be better approach but this is one costy way to do this.
$firms = import-csv C:\firm.csv -Header 'Firm','Town','Tel','Mail' -Delimiter ';'
$Result = #()
ForEach($i in $firms){
$found = 0;
ForEach($m in $Result){
if($m.Firm -eq $i.Firm){
$found = 1
if( $i.Mail.length -ne 0 )
{
$m.Mail = $i.Mail
}
break;
}
}
if($found -eq 0){
$Result += [pscustomobject] #{Firm=$i.Firm; Town=$i.Town; Tel=$i.Tel; Mail=$i.Mail}
}
}
$Result | export-csv C:\out.csv

compare two csv using powershell and return matching and non-matching values

I have two csv files, i want to check the users in username.csv matches with userdata.csv copy
to output.csv. If it does not match return the name alone in the output.csv
For Ex: User Data contains 3 columns
UserName,column1,column2
Hari,abc,123
Raj,bca,789
Max,ghi,123
Arul,987,thr
Prasad,bxa,324
username.csv contains usernames
Hari
Rajesh
Output.csv should contain
Hari,abc,123
Rajesh,NA,NA
How to achieve this. Thanks
Sorry for that.
$Path = "C:\PowerShell"
$UserList = Import-Csv -Path "$($path)\UserName.csv"
$UserData = Import-Csv -Path "$($path)\UserData.csv"
foreach ($User in $UserList)
{
ForEach ($Data in $UserData)
{
If($User.Username -eq $Data.UserName)
{
# Process the data
$Data
}
}
}
This returns only matching values. I also need to add the non-matching values in output
file. Thanks.
something like this will work:
$Path = "C:\PowerShell"
$UserList = Import-Csv -Path "$($path)\UserName.csv"
$UserData = Import-Csv -Path "$($path)\UserData.csv"
$UserOutput = #()
ForEach ($name in $UserList)
{
$userMatch = $UserData | where {$_.UserName -eq $name.usernames}
If($userMatch)
{
# Process the data
$UserOutput += New-Object PsObject -Property #{UserName =$name.usernames;column1 =$userMatch.column1;column2 =$userMatch.column2}
}
else
{
$UserOutput += New-Object PsObject -Property #{UserName =$name.usernames;column1 ="NA";column2 ="NA"}
}
}
$UserOutput | ft
It loops through each name in the user list. Line 9 does a search of the userdata CSV for a matching user name if it finds it it adds the user data for that user to the output if no match is found it adds the user name to the output with NA in both columns.
had to change your userList csv:
usernames
Hari
Rajesh
expected output:
UserName column1 column2
-------- ------- -------
Hari abc 123
Rajesh NA NA
I had a similar situation, where I needed a "changed record collection" holding the entire record when the current record was either new or had any changes when compared to the previous record. This was my code:
# get current and previous CSV
$current = Import-Csv -Path $current_file
$previous = Import-Csv -Path $previous_file
# collection with new or changed records
$deltaCollection = New-Object Collections.Generic.List[System.Object]
:forEachCurrent foreach ($row in $current) {
$previousRecord = $previous.Where( { $_.Id -eq $row.Id } )
$hasPreviousRecord = ($null -ne $previousRecord -and $previousRecord.Count -eq 1)
if ($hasPreviousRecord -eq $false) {
$deltaCollection.Add($current)
continue forEachCurrent
}
# check if value of any property is changed when compared to the previous
:forEachCurrentProperty foreach ($property in $current.PSObject.Properties) {
$columnName = $property.Name
$currentValue = if ($null -eq $property.Value) { "" } else { $property.Value }
$previousValue = if ($hasPreviousRecord) { $previousRecord[0]."$columnName" } else { "" }
if ($currentValue -ne $previousValue -or $hasPreviousRecord -eq $false) {
$deltaCollection.Add($currentCenter)
continue forEachCurrentProperty
}
}
}