Send 1 email to each person with all their codes - powershell

I need to write a script that sends a single email to each person.
That email will have unique codes for the person.
Each person can get any number of codes.
The data looks like this
Email,Codes
Email#dom.com,213
Email#dom.com,999
Email2#dom.com,111
Email2#dom.com,123
Email2#dom.com,643
Email2#dom.com,809
Email2#dom.com,722
Email3#dom.com,013
I think the script will go something like this.
#get the data
$PeopleAndCodes = Import-Csv C:\temp\PeopleCodes.csv
#Count how many groups of unique people
[array]$CountOfPeople = $PeopleAndCodes.email | Group-Object -noelement | Select-Object Count
#Loop through unique people
$Index = 0;
while ($Index -lt $CountOfPeople.count) {
$Index++
#THIS BELOW EXITS BEFORE GETTING THROUGH ALL THE CODES FOR ONE PERSON
[int]$DCodes = 0
foreach ($DCodes in [int]$CountOfPeople[$DCodes].count) {
$DCodes++
Write-Host $DCodes DCODES
Write-Host $CountOfPeople[$DCodes].count CountOfPeople
Write-Host $PeopleAndCodes[$DCodes].codes
}
}
The problem is that my 2nd loop stops as soon as number of unique people is reached and then moves to the next person.
I don't understand why the 2nd loop is not going through the codes and then to the next person?

You are very close, I would make some small tweaks to your code and we can get it there.
First, instead of indexing through the array, let's select all of the unique e-mails from the list of codes.
$uniqueUsers = $PeopleAndCodes | select -Unique Email
Next, we can foreach loop our way through the list of $uniqueUsers, and for each, find any matching codes for them.
foreach($uniqueUser in $uniqueUsers){
$thisEmail = $uniqueUser.Email
$matchingCodes = $PeopleAndCodes | Where Email -Match $uniqueUser.Email |
Select-Object -ExpandProperty Codes
Now we have within this loop a $thisEmail var that holds the email address of the user, and then an array of all of the matching codes for the user, called $matchingCodes. We don't need to index through these either. In fact that second loop was likely causing the issue, as there are more items in the list than unique users.
Your limiting condition was the number of unique users, not the number of items in the list.
So, to avoid confusion and get the desired output, just remove that second loop entirely since it isn't helping us.
Write-Host "Person - $thisEmail"
Write-Host "Person has $($matchingCodes.Count) codes"
$matchingCodes -join ","
Gives an output of
Person - Email#dom.com
Person has 2 codes
213,999
--------------------
Person - Email2#dom.com
Person has 5 codes
111,123,643,809,722
Completed Code
$PeopleAndCodes = Import-Csv C:\temp\PeopleCodes.csv
$uniqueUsers = $PeopleAndCodes | select -Unique Email
foreach($uniqueUser in $uniqueUsers){
$thisEmail = $uniqueUser.Email
$matchingCodes = $PeopleAndCodes | where Email -Match $uniqueUser.Email | Select-Object -ExpandProperty Codes
Write-Host "Person - $thisEmail"
Write-Host "Person has $($matchingCodes.Count) codes"
$matchingCodes -join ","
Write-Host "--------------------"
}

I want to point out that while above is the best answer. In the comments is another answer that works with my original code as well.
#get the data
$PeopleAndCodes = Import-Csv C:\temp\PeopleCodes.csv
#Count how many groups of unique people
[array]$CountOfPeople = $PeopleAndCodes.email | Group-Object -noelement | Select-Object Count
#Loop through unique people
$Index = 0;
while ($Index -lt $CountOfPeople.count) {
$Index++
#This loops through each person and gets their code(s)
[int]$DCodes = 0
foreach ($DCodes in 0..[int]$CountOfPeople[$DCodes].count) {
Write-Host $PeopleAndCodes[$DCodes].email
Write-Host $PeopleAndCodes[$DCodes].codes
$DCodes++
}
}

Related

combined two arrays of different size with different properties

My goal is to get all group settings and add the total group member count for each group along with the total owners, managers for each group.
Either array can be larger or they can be equal.
I start with getting group member count.
$GroupMembersCount = gam print groups domain <domain.com> members managers owners countsonly | ConvertFrom-Csv
Then I get the settings for each group
$GroupSettings = gam print groups settings | ConvertFrom-Csv
Both arrays share the email property and nothing else.
The $GroupMembersCount is just a count for all the members and what right they have in each group.
looks like
email, manager, member
address.com, 1, 5
newaddress.com, 0,0
anotheraddress,3,3
etc...
the other array $GroupSettings has like 30 column headers and one header is email addresses and the other headers are a bunch of true or false depending on the setting for the group.
I want to combine both arrays into 1.
I know I need to do something like create a new array but the problem I have is how do I combine them since I do not know which will be larger?
$result = $null
$result = $GroupSettings + $GroupMembersCount | Select-Object -Unique
Above did not help
$Max = $null
$Max = ($GroupSettings.Count, $GroupMembersCount.count | Measure-Object -Maximum).Maximum
$resultsarray = $null
$resultsarray = For ($i = 0, $I -lt $max, $I++) {
do stuff
}
If I do something like above which array am I going to index through to get them all ?
The End result will tell me
Which setting each group has, its total members, its total managers and its total owners.
I need this so I can filter groups that have no owners and managers along with other key settings
Use a hashtable to "index" the first data set based on the email address:
$GroupMemberCount = gam print groups domain <domain.com> members managers owners countsonly | ConvertFrom-Csv
$GroupMemberCountByEmail = #{}
$GroupMemberCount |ForEach-Object {
$GroupMemberCountByEmail[$_.email] = $_
}
Now we can use Select-Object to attach the relevant counts to each group setting object based on the email property:
$result = $GroupSettings |Select *,#{Name='MemberCount';Expression={[int]$GroupMemberCountByEmail[$_.email].member}},#{Name='ManagerCount';Expression={[int]$GroupMemberCountByEmail[$_.email].manager}}
Above explained the answer, here I am going to break down what I did and learned for anyone passing by with a similar problem.
What I did:
$GroupMembersCount = $null
$GroupMembersCount = gam print groups domain <domain.com> members managers owners countsonly | ConvertFrom-Csv
$GroupSettings = $null
$GroupSettings = gam print groups settings | ConvertFrom-Csv
$GroupMemberCountByEmail = #{}
$GroupMembersCount | ForEach-Object {
$GroupMemberCountByEmail[$_.email] = $_
}
$GroupSettings | Select-Object *,
#{
Name = 'MembersCount'
Expression = { [int]$GroupMemberCountByEmail[$_.email].MembersCount }
},#{
Name = 'ManagersCount'
Expression = { [int]$GroupMemberCountByEmail[$_.email].ManagersCount }
},#{
Name = 'OwnersCount'
Expression = { [int]$GroupMemberCountByEmail[$_.email].OwnersCount }
} |
Export-Csv 'c:\temp\groups.csv' -NoTypeInformation -Encoding UTF8
The above works. The first part, what #Mathias had me build, was a hash table.
The beauty is in the $result, here, Mathias uses calculated properties. One key point in the $result line is as follows:
#{
Name = 'ManagersCount'
Expression = { [int]$GroupMemberCountByEmail[$_.email].ManagersCount }
}
The tail of each calculated property, like .managerscount part has to be the value in the hash created above.
There is plenty here I don't understand but, if you are looking hopefully it will lead you like it did for me.

Add random users to groups from CSV file

I have a question, i'm adding users from a csv file to some groups, in this ex. it's MAX. 3 users/group.But what I want is, that I have 10 groups and then adding the random users from the csv file to the groups, then maybe i got 7 groups with 4 users and then last 3 with 3 users and thats OK.
But how do I changes this script, from just adding 3 users/group, to adding users to the "hardcoded" 10 groups, from the csv file?
ATM. I got this:
$deltager = Import-Csv C:\Users\Desktop\liste.csv
$holdstr = 3
$maxTeams = [math]::ceiling($deltager.Count/$holdstr)
$teams = #{}
$shuffled = $deltager | Get-Random -Count $deltager.Count
$shuffled | ForEach-Object { $i = 1 }{
$teams["$([Math]::Floor($i / $holdstr))"] += #($_.Navn)
$i++
}
$Grupper = $teams | Out-String
Write-Host $Grupper
Use the remainder/modulo operator % to "wrap around" the group index, then simply add one user at a time for optimum distribution:
# define number of groups
$numberOfTeams = 10
# read participant records from csv
$participants = Import-Csv C:\Users\Desktop\liste.csv
# create jagged array for the team rosters
$teams = ,#() * $numberOfTeams
# go through participant list, add to "next" group in line
$index = 0
$participants |%{
$teams[$index++ % $numberOfTeams] += #($_.Navn)
}
If you want to randomize the order of participants, simply sort the list randomly before populating the groups:
$participants = Import-Csv C:\Users\Desktop\liste.csv |Sort-Object {Get-Random}
Each item in $teams will be another array of names, so to enumerate them:
0..$teams.Length |%{
Write-Host "Team $($_+1):" $($teams[$_] -join ', ')
}

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.

Using Powershell to Loop check usernames from csv, loop through AD and add a number if needed

Here is my scenario,
I wrote a SQL query that extracts user accounts with a null username from our student information system. Lets just assume these are newly enrolled students. I then want to take this list, which has a column of suggested usernames, done by simple concatenation in the SQL query. I want to loop through that csv and check to make sure that username doesn't already exist in AD , if it does, append the next available number to the username.
So in my test environment I have a csv that looks like this. ( I made this up for testing)
StudentID,First,Last,SuggestedUsername
12345,tony,Test,testto
54321,tolly,test,testto
I my test AD environment I already have a student named Tommy Test or Testto, so in this case, my powershell script should tell me Tony Test should be testto1 and Tolly Test should be testto2. Is this making sense?
The meat of my script works, It will read the csv, loop through AD and return testto1 for line 1 of the csv, the problem is it will not read line 2, the script ends
I have been playing around with the arrays in the script but here is what I have so far
Import-module Activedirectory
Add-Pssnapin Quest.ActiveRoles.admanagement
$useraccounts =#()
$useraccounts = import-Csv "Path\Test.csv"
$userbase = Get-QADuser -sizelimit 0 -SearchRoot 'mydomain.com/OU'
foreach ($user in $useraccounts) {
if ($userbase)
{
$userbase = $userbase | Select samaccountname | %{($_ -split "#")[0]}
$UserNumbers = #()
$userbase | % {
if ($_ -Match '\d+')
{
$UserNumbers += $matches[0]
}
}
$MaxUserNumber = ($userNumbers | Measure-Object -max).Maximum
$suggestedUserName = $user+($MaxUserNumber+1)
}
Else
{
$SuggestedUserName = $user
}
}
Write-Host $suggestedUserName
Ok, your loop doesn't appear to be cycling because you aren't using an array of strings as your output, you are just using a string, so it just shows up with the last loop. But if you like my solution that's neither here nor there, because I think I have a better option for you. Just for the sake of simplicity, back up your CSV and then delete the Suggested Name column and run this against that.
$Users = Import-CSV "path\test.csv"
$Users|%{$_|Add-Member UserID ("$($_.last)$($_.first.substring(0,2))")}
$NameConflicts = $Users|group userid|?{$_.count -gt 1}
ForEach($Name in $NameConflicts){
$x=0
if(dsquery user -samid "$($name.name)*"){
$x = Get-ADUser -Filter {samaccountname -like "$($Name.Name)*"}|%{$_.samaccountname -replace "($($Name.name))(\d*)","`$2"}|sort -Descending |select -first 1
}
For($i=if($x -gt 0){0}else{1};$i -lt ($Name.count);$i++){
$Name.Group[$i].UserID = "$($Name.Name)$($i+$x)"
}
}
$Users|group userid|?{$_.count -eq 1}|%{if(dsquery user -samid "$($_.name)"){$_.Group[0].UserID = "$($_.Name)1"}}
$Users|FT -auto
That loads your list, creates potential user names by taking the last name and the first two letters of the first name. Then it groups by that, and finds any of them that have more than one. Then for each of those it checks AD for any existing accounts with names like that, and takes the number off the end of the name, and selects the highest one. Then if it found any already in existence it renames all of the potential user names to append the next available number to the end (if none are found in AD it leaves the first one, and renames the rest of them). Lastly it checks all of the other names, where there is just one user name, and if it is in AD already it adds a 1 to the end of the user id.
This should run faster than your script, because I'm only searching for names that are needed to be checked, and it's filtering at the provider level instead of taking all names, and then filtering through powershell.

compare columns in two csv files

With all of the examples out there you would think I could have found my solution. :-)
Anyway, I have two csv files; one with two columns, one with 4. I need to compare one column from each one using powershell. I thought I had it figured out but when I did a compare of my results, it comes back as false when I know it should be true. Here's what I have so far:
$newemp = Import-Csv -Path "C:\Temp\newemp.csv" -Header login_id, lastname, firstname, other | Select-Object "login_id"
$ps = Import-Csv -Path "C:\Temp\Emplid_LoginID.csv" | Select-Object "login id"
If ($newemp -eq $ps)
{
write-host "IDs match" -forgroundcolor green
}
Else
{
write-host "Not all IDs match" -backgroundcolor yellow -foregroundcolor black
}
I had to specifiy headers for the first file because it doesn't have any. What's weird is that I can call each variable to see what it holds and they end up with the same info but for some reason still comes up as false. This occurs even if there is only one row (not counting the header row).
I started to parse them as arrays but wasn't quite sure that was the right thing. What's important is that I compare row1 of the first file with with row1 of the second file. I can't just do a simple -match or -contains.
EDIT: One annoying thing is that the variables seem to hold the header row as well. When I call each one, the header is shown. But if I call both variables, I only see one header but two rows.
I just added the following check but getting the same results (False for everything):
$results = Compare-Object -ReferenceObject $newemp -DifferenceObject $ps -PassThru | ForEach-Object { $_.InputObject }
Using latkin's answer from here I think this would give you the result set you're looking for. As per latkin's comment, the property comparison is redundant for your purposes but I left it in as it's good to know. Additionally the header is specified even for the csv with headers to prevent the header row being included in the comparison.
$newemp = Import-Csv -Path "C:\Temp\_sotemp\Book1.csv" -Header loginid |
Select-Object "loginid"
$ps = Import-Csv -Path "C:\Temp\_sotemp\Book2.csv" -Header loginid |
Select-Object "loginid"
#get list of (imported) CSV properties
$props1 = $newemp | gm -MemberType NoteProperty | select -expand Name | sort
$props2 = $ps | gm -MemberType NoteProperty | select -expand Name | sort
#first check that properties match
#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 $newemp $ps -Property $props1
}
In the second line, I see there a space "login id" and the first line doesn't have it. Could that be an issue. Try having the same name for the headers in the .csv files itself. And it works for without providing header or select statements. Below is my experiment based upon your input.
emp.csv
loginid firstname lastname
------------------------------
abc123 John patel
zxy321 Kohn smith
sdf120 Maun scott
tiy123 Dham rye
k2340 Naam mason
lk10j5 Shaan kelso
303sk Doug smith
empids.csv
loginid
-------
abc123
zxy321
sdf120
tiy123
PS C:\>$newemp = Import-csv C:\scripts\emp.csv
PS C:\>$ps = Import-CSV C:\scripts\empids.csv
PS C:\>$results = Compare-Object -ReferenceObject $newemp -DifferenceObject $ps | foreach { $_.InputObject}
Shows the difference objects that are not in $ps
loginid firstname lastname SideIndicator
------- --------- -------- -------------
k2340 Naam mason <=
lk10j5 Shaan kelso <=
303sk Doug smith <=
I am not sure if this is what you are looking for but i have used the PowerShell to do some CSV formatting for myself.
$test = Import-Csv .\Desktop\Vmtools-compare.csv
foreach ($i in $test) {
foreach ($n in $i.name) {
foreach ($m in $test) {
$check = "yes"
if ($n -eq $m.prod) {
$check = "no"
break
}
}
if ($check -ne "no") {$n}
}
}
this is how my excel csv file looks like:
prod name
1 3
2 5
3 8
4 2
5 0
and script outputs this:
8
0
so basically script takes each number under Name column and then checks it against prod column. If the number is there then it won't display else it will display that number.
I have also done it the opposite way:
$test = Import-Csv c:\test.csv
foreach ($i in $test) {
foreach ($n in $i.name) {
foreach ($m in $test) {
$check = "yes"
if ($n -eq $m.prod) {echo $n}
}
}
}
this is how my excel csv looks like:
prod name
1 3
2 5
3 8
4 2
5 0
and script outputs this:
3
5
2
so script shows the matching entries only.
You can play around with the code to look at different columns.