How to deal with automated duplicate user removal - csv

I have the following:
#(Import-Csv C:\Users\Administrator\Desktop\dbs\Monday.csv) +
#(Import-Csv C:\Users\Administrator\Desktop\dbs\Tuesday.csv) +
#(Import-Csv C:\Users\Administrator\Desktop\dbs\Wednesday.csv) +
#(Import-Csv C:\Users\Administrator\Desktop\dbs\Thursday.csv) +
#(Import-Csv C:\Users\Administrator\Desktop\dbs\Friday.csv) |
sort first_name,last_name,phone1 -Unique |
Export-Csv C:\Users\Administrator\Desktop\dbs\joined.csv
Import-Module ActiveDirectory
#EDIT PATH SO IT POINTS TO DB FILE \/
$newUserList = Import-Csv C:\Users\Administrator\Desktop\dbs\joined.csv
ForEach ($item in $newUserList){
$fname = $($item.first_name)
$lname = $($item.last_name)
$phone = $($item.phone1)
$username=$fname+$lname.substring(0,1)
# Puts Domain name into a Placeholder.
$domain='#csilab.local'
# Build the User Principal Name Username with Domain added to it
$UPN=$username+$domain
# Create the Displayname
$Name=$fname+" "+$lname
$newusers1 = (New-ADUser -GivenName $fname -Surname $lname -HomePhone $phone -Name $Name -DisplayName $Name -SamAccountName $username -AccountPassword (ConvertTo-SecureString "1NewPassword" -asplaintext -force) -ChangePasswordAtLogon $true -UserPrincipalName $UPN -Path "ou=test,dc=csi,dc=lab" -Enabled $true -PassThru) |
# I need this block to check for duplicates missed by the csv sort & merge
# as well as any in the destination OU itself as the script will run daily
# with inevitable possibility that user is unique to the csv but not the AD.
$newusers1 | Get-ADUser -Filter * -SearchBase "OU=Active Users,DC=csilab,DC=local" |
Sort-Object -Unique |
Remove-ADUser -confirm:$false
However I run it and get:
Get-ADUser : The input object cannot be bound to any parameters for the command
either because the command does not take pipeline input or the input and its
properties do not match any of the parameters that take pipeline input.
At C:\Users\Administrator\Desktop\Team2.ps1:40 char:14
+ $newusers1 | Get-ADUser -Filter * -SearchBase "OU=Active Users,DC=csilab,DC=loca ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (CN=Bethanie Cut...csilab,dc=local:PSObject) [Get-ADUser], ParameterBindingException
+ FullyQualifiedErrorId : InputObjectNotBound,Microsoft.ActiveDirectory.Management.Commands.GetADUser
I also worry even if it did work that it'd delete the unique users instead of duplicates.
get-AdUser $username | Move-ADObject -TargetPath 'OU=Active Users,dc=csilab,dc=local'
}
What can I do to ensure all users are there without any originals getting deleted, just the duplicates?

You still have an empty pipe at the end of your New-ADUser statement, which would cause your script to fail with an "empty pipe element is not allowed" error, but oh well ...
To avoid collisions just check if an account already exists before you try to create it, and create it only if it doesn't:
$username = $fname + $lname.substring(0,1)
...
if (Get-ADUser -Filter "SamAccountName -eq '$username'") {
Write-Host "Account $username already exists."
} else {
New-ADUser -SamAccountName $username -Name $Name ... -PassThru
}
Also, you're overcomplicating the CSV handling. Simply process a list of the files via ForEach-Object:
$domain = '#csilab.local'
Set-Location 'C:\Users\Administrator\Desktop\dbs'
'Monday.csv', 'Tuesday.csv', 'Wednesday.csv', 'Thursday.csv', 'Friday.csv' |
ForEach-Object { Import-Csv $_ } |
Sort-Object first_name, last_name, phone1 -Unique |
ForEach-Object {
$fname = $_.first_name
$lname = $_.last_name
$phone = $_.phone1
...
}

You might want to use Try/Catch:
Try {$newusers1=(New-ADUser–GivenName$fname–Surname$lname-HomePhone$phone–Name$Name-DisplayName $Name –SamAccountName$username–AccountPassword(ConvertTo-SecureString"1NewPassword"-asplaintext-force) -ChangePasswordAtLogon$true–UserPrincipalName$UPN–Path"dc=csi,dc=lab"-Enabled$true)}
Catch {
If ($_.Exception.Message -match "account already exists")
{
#do whatever here, eg $NewUsers1 = Get-ADUser $Name
}
}
Also, if you can't see the user when browsing via ADUC it could be that you are connected to a different DC.
As mentioned above, the newuser1 variable will be null if the command failed. It will not load with the other user automatically, and it would be scary bad if it did. You need to decide what to do if the account already exist, that may simply be loading the variable with the other account, or doing something like appending a "1" to $name and retrying the command.

Related

Powershell and filenames with non-ASCII characters (e.g. Æ)

I am attempting to index my movie collection and in doing so have run across an issue where at least one title is skipped in the import phase due to special characters. The code skips over "Æon Flux" due to it starting with Æ. Would anyone know how to correct this, please?
Clear-Host
# Variables:
$movie_dir = "K:\Movies"
# Because reasons...
$PSDefaultParameterValues['*:Encoding'] = 'utf8'
# Connect to the library MySQL.Data.dll
Add-Type -Path 'C:\Program Files (x86)\MySQL\Connector NET 8.0\Assemblies\v4.8\MySql.Data.dll'
# Create a MySQL Database connection variable that qualifies:
$Connection = [MySql.Data.MySqlClient.MySqlConnection]#{ConnectionString='server=127.0.0.1;uid=username;pwd=password;database=media'}
$Connection.Open()
# Drop the table to clear all entries.
$sql_drop_table = New-Object MySql.Data.MySqlClient.MySqlCommand
$sql_drop_table.Connection = $Connection
$sql_drop_table.CommandText = 'DROP TABLE Movies'
$sql_drop_table.ExecuteNonQuery() | Out-Null
# (Re)create the table.
$sql_create_table = New-Object MySql.Data.MySqlClient.MySqlCommand
$sql_create_table.Connection = $Connection
$sql_create_table.CommandText = 'create table Movies(movie_id INT NOT NULL AUTO_INCREMENT, movie_title VARCHAR(255) NOT NULL, movie_file_date INT, movie_IMDB_id INT, PRIMARY KEY (movie_id))'
$sql_create_table.ExecuteNonQuery() | Out-Null
$movies = Get-ChildItem $movie_dir -File -include *.mp4 -Recurse -Depth 1 |
Select-Object -ExpandProperty FullName |
Sort-Object |
Get-Unique |
where{$_ -ne ""}
foreach ($movie in $movies)
{
# .net function to get just the filename (movie title).
$title = [System.IO.Path]::GetFileNameWithoutExtension($movie)
# Get the creation date of the movie and reformat it to yearmonthday.
$add_date = (Get-ChildItem $movie).CreationTime.toString("yyyyMMdd")
$query = "INSERT INTO Movies(movie_id, movie_title, movie_file_date) VALUES(NULL, #title, $add_date)"
$command = $connection.CreateCommand()
$command.CommandText = $query
# Sanatize single quotes in filenames for input.
$command.Parameters.AddWithValue("#title", $title) | Out-Null
$command.ExecuteNonQuery() | Out-Null
}
# Close the MySQL connection.
$Connection.Close()
Write-Host
Write-Host("Added") $movies.Count ("movies.")
I don't think it is the Get-ChildItem that skips the file with that special character. More likely, you need to tell your MySql to use UTF-8.
For that, have a look at How to make MySQL handle UTF-8 properly
As for your code, I would change this:
$movies = Get-ChildItem $movie_dir -File -include *.mp4 -Recurse -Depth 1 |
Select-Object -ExpandProperty FullName |
Sort-Object |
Get-Unique |
where{$_ -ne ""}
into
$movies = Get-ChildItem -Path $movie_dir -File -Filter '*.mp4' -Recurse -Depth 1 | Sort-Object -Property FullName
and work with the FileInfo objects from there on:
foreach ($movie in $movies) {
$title = $movie.BaseName
# Get the creation date of the movie and reformat it to yearmonthday.
$add_date = '{0}:yyyyMMdd}' -f $movie.CreationTime
. . .
}
Though Æ is not an ASCII character it is not otherwise "special", so I edited the question title and tags to reflect that.
ExecuteNonQuery() returns the number of rows affected by the command; in the case of $command, it's the number of rows inserted. You are discarding this value, however...
$command.ExecuteNonQuery() | Out-Null
...which masks the problem in the event the INSERT fails. Instead, test the result and respond appropriately...
if ($command.ExecuteNonQuery() -eq 1)
{
Write-Host -Message "Successfully inserted movie ""$title""."
}
else
{
Write-Warning -Message "Failed to insert movie ""$title""."
}
This will make it clear if the issue lies in interacting with the filesystem or the database.
Some other notes:
MySqlCommand implements the IDisposable interface and so each instance should be disposed when you're done using it...
$query = "INSERT INTO Movies(movie_id, movie_title, movie_file_date) VALUES(NULL, #title, $add_date)"
$command = $connection.CreateCommand()
try
{
$command.CommandText = $query
# Sanatize single quotes in filenames for input.
$command.Parameters.AddWithValue("#title", $title) | Out-Null
if ($command.ExecuteNonQuery() -eq 1)
{
Write-Host -Message "Successfully inserted movie ""$title""."
}
else
{
Write-Warning -Message "Failed to insert movie ""$title""."
}
}
finally
{
$command.Dispose()
}
...and the same for $sql_drop_table and $sql_create_table. The code in the finally block will run even if an error is thrown from within the try block.
See Difference with Parameters.Add and Parameters.AddWithValue and its links for why AddWithValue() can be problematic.
Instead of...
Write-Host("Added") $movies.Count ("movies.")
...a more typical way to build this message would be with string interpolation...
Write-Host "Added $($movies.Count) movies."
...or the format operator...
Write-Host ('Added {0} movies.' -f $movies.Count)
You can also incorporate numeric format strings, so if $movies.Count is 1234 and $PSCulture is 'en-US' then...
Write-Host "Added $($movies.Count.ToString('N0')) movies."
...and...
Write-Host ('Added {0:N0} movies.' -f $movies.Count)
...will both write...
Added 1,234 movies.

Import CSV and updating specific lines

So I have a script that runs at logon to search for PST's on a users machine, then copies them to a holding area waiting for migration.
When the search/copy is complete it outputs to a CSV that looks something like this:
Hostname,User,Path,Size_in_MB,Creation,Last_Access,Copied
COMP1,user1,\\comp1\c$\Test PST.pst,20.58752,08/12/2015,08/12/2015,Client copied
COMP1,user1,\\comp1\c$\outlook\outlook.pst,100,08/12/2015,15,12,2015,In Use
The same logon script has an IF to import the CSV if the copied status is in use and makes further attempts at copying the PST into the holding area. If it's successful it exports the results to the CSV file.
My question is, is there anyway of getting it to either amend the existing CSV changing the copy status? I can get it to add the new line to the end, but not update.
This is my 'try again' script:
# imports line of csv where PST file is found to be in use
$PST_IN_USE = Import-CSV "\\comp4\TEMPPST\PST\$HOSTNAME - $USER.csv" | where { $_.copied -eq "In Use" }
ForEach ( $PST_USE in $PST_IN_USE )
{ $NAME = Get-ItemProperty $PST_IN_USE.Path | select -ExpandProperty Name
$NEW_NAME = $USER + "_" + $PST_IN_USE.Size_in_MB + "_" + $NAME
# attempts to copy the file to the pst staging area then rename it.
TRY { Copy-Item $PST_IN_USE.Path "\\comp4\TEMPPST\PST\$USER" -ErrorAction SilentlyContinue
Rename-Item "\\comp4\TEMPPST\PST\$USER\$NAME" -NewName $NEW_NAME
# edits the existing csv file replacing "In Use" with "Client Copied"
$PST_IN_USE.Copied -replace "In Use","Client Copied"
} # CLOSES TRY
# silences any errors.
CATCH { }
$PST_IN_USE | Export-Csv "\\comp4\TEMPPST\PST\$HOSTNAME - $USER.csv" -NoClobber -NoTypeInformation -Append
} # CLOSES ForEach ( $PST_USE in $PST_IN_USE )
This is the resulting CSV
Hostname,User,Path,Size_in_MB,Creation,Last_Access,Copied
COMP1,user1,\\comp1\c$\Test PST.pst,20.58752,08/12/2015,08/12/2015,Client copied
COMP1,user1,\\comp1\c$\outlook\outlook.pst,100,08/12/2015,15,12,2015,In Use
COMP1,user1,\\comp1\c$\outlook\outlook.pst,100,08/12/2015,15,12,2015,Client copied
It's almost certainly something really simple, but if it is, it's something I've yet to come across in my scripting. I'm mostly working in IF / ELSE land at the moment!
If you want to change the CSV file, you have to write it completely again, not just appending new lines. In your case this means:
# Get the data
$data = Import-Csv ...
# Get the 'In Use' entries
$inUse = $data | where Copied -eq 'In Use'
foreach ($x in $inUse) {
...
$x.Copied = 'Client Copied'
}
# Write the file again
$data | Export-Csv ...
The point here is, you grab all the lines from the CSV, modify those that you process and then write the complete collection back to the file again.
I've cracked it. It's almost certainly a long winded way of doing it, but it works and is relatively clean too.
#imports line of csv where PST file is found to be in use
$PST_IN_USE = Import-CSV "\\comp4\TEMPPST\PST\$HOSTNAME - $USER.csv" | where { $_.copied -eq "In Use" }
$PST_IN_USE | select -ExpandProperty path | foreach {
# name of pst
$NAME = Get-ItemProperty $_ | select -ExpandProperty Name
# size of pst in MB without decimals
$SIZE = Get-ItemProperty $_ | select -ExpandProperty length | foreach { $_ / 1000000 }
# path of pst
$PATH = $_
# new name of pst when copied to the destination
$NEW_NAME = $USER + "_" + $SIZE + "_" + $NAME
TRY { Copy-Item $_ "\\comp4\TEMPPST\PST\$USER" -ErrorAction SilentlyContinue
TRY { Rename-Item "\\comp4\TEMPPST\PST\$USER\$NAME" -NewName $NEW_NAME -ErrorAction SilentlyContinue | Out-Null }
CATCH { $NEW_NAME = "Duplicate exists" }
$COPIED = "Client copied" }
CATCH { $COPIED = "In use" ; $NEW_NAME = " " }
$NEW_FILE = Test-Path "\\comp4\TEMPPST\PST\$HOSTNAME - $USER 4.csv"
IF ( $NEW_FILE -eq $FALSE )
{ "Hostname,User,Path,Size_in_MB,Creation,Last_Access,Copied,New_Name" |
Set-Content "\\lccfp1\TEMPPST\PST\$HOSTNAME - $USER 4.csv" }
"$HOSTNAME,$USER,$PATH,$SIZE,$CREATION,$LASTACCESS,$COPIED,$NEW_NAME" |
Add-Content "\\comp4\TEMPPST\PST\$HOSTNAME - $USER 4.csv"
} # CLOSES FOREACH #
$a = Import-CSV "\\comp4\TEMPPST\PST\$HOSTNAME - $USER.csv" | where { $_.copied -ne "in use" }
$b = Import-Csv "\\comp4\TEMPPST\PST\$HOSTNAME - $USER 4.csv"
$a + $b | export-csv "\\comp4\TEMPPST\PST\$HOSTNAME - $USER 8.csv" -NoClobber -NoTypeInformation
Thanks for the help. Sometimes it takes a moments break and a large cup of coffee to see things a different way.

PowerShell Import CSV to add Active Directory Security Groups

I have a CSV from a client with a few columns in it. A column for Email, SamAccountName, SecurityGroups.
In the Security Group column, each Security Group is separated by a ','
Each row would look like below
User1 User1#email.com SecurityGroup1, SecurityGroup2, SecurityGroup3
User2 User2#email.com SecurityGroup1, SecurityGroup2, SecurityGroup3
User3 User3#email.com SecurityGroup13, SecurityGroup5, SecurityGroup6
A list of users in different Security groups, how do I go about reading the CSV and only adding the Security Groups each user needs.
Below is what I have so far, when I run it in WhatIf mode it adds all the security groups to one user.
Clear
$ADGroup = $null
$i = 0
$ADGroup = import-csv 'File.csv'
$sADGroup = $ADGroup.'Groups required'
Import-CSV "TestUsers.csv" | % {
$SAM = $_.SamAccountName
$ADUser = Get-Aduser -filter {SamAccountName -eq $SAM} -Properties * | Select SamAccountName, MemberOf
Write-Host 'SamAccountName :' $_.SamAccountName ' - ADUser - ' $SAM
$sADGroup | ForEach-Object{
Write-HOst $ADGroup.ADgroup[$i]
Add-ADGroupMember $sADGroup.split(",")[$i] -Members $ADUser.SamAccountName -WhatIf
$i += 1
}
}
Here is the final script, using AD-ADPrincipalGroupMembership was the find, instead of using Add-ADGroupMember
Import-Csv 'testusers.csv' |
ForEach-Object {
add-content $LogFile "Adding User to Group - $($_.Name)"
Try{
Add-ADPrincipalGroupMembership -identity $_.'SamAccountName' -memberof ($_.'Groups' -split', ')
}
Catch {
Add-content $LogFile "Error adding user to group - $($_.Name)"
}
}

How to update AD Info attribute to list groups user was deleted from?

I have this script that will only list a user's groups on the ISE screen where the data can be copied and pasted elsewhere, but I'm trying to get the group membership names written into the Telephone Notes tab (or Info field). I'm thinking next that these probably need to be turned into string values since I'm getting errors about multi properties not allowed. Here is what I've been trying, but I keep getting errors. Thanks
Import-Module ActiveDirectory
$Users= Import-csv "C:\Scripts\UsersSAM-DisplayName.csv"
ForEach ($User in $Users) {
$SamAccountName=$User.SamAccountName
$DisplayName=$User.DisplayName
$TableFormat= #{E={$_.Name};L="$($DisplayName) - $($SamAccountName)"}
Get-ADUser -Identity $SamAccountName -Properties MemberOf | % {$_.MemberOf } | % {Get-ADGroup -Identity $_ } | % { Set-ADUser -Identity $SamAccountName -add #{info="$_.name"}} | Select Name |
Format-Table $TableFormat }
I figured this out. What they wanted was to first write out a terminated user's groups, then remove those. I did it like this and this code includes the semi-colon so if the user comes back, all you need to do to add them back to all the groups is copy and paste those from the output stored in the Telephones Tab, Notes field. I've also used a trimmed down version of this to export a user's groups to speed up duplicating a user's groups so they match with others on the same team. Hope this helps someone.
Import-csv "$Terms" | % {
$user = Get-ADUser -LDAPFilter ("(sAMAccountName=" + $_.samaccountname + ")") -Properties samaccountname,enabled,name,memberof,distinguishedname,info
#Grab all user group names
$user | ForEach-Object {
$grps = $_.MemberOf | Get-ADGroup | ForEach-Object {$_.Name} | Sort-Object
$arec = $_.Name,$_.SamAccountName
$aline = ($grps -join ";")
#Add info to Notes field Telephone Tab
Get-ADPrincipalGroupMembership -Identity $user | %{
If ($_.SamAccountName -ne "Domain Users") {
$Userinfo=$user.info
Set-ADUser $User -replace #{info= "$Userinfo | $a | Terminated via automated process | $aline"}
#Remove User Groups Process in Telephones Tab Notes Field.
Remove-ADPrincipalGroupMembership -Identity $user -MemberOf $_.SamAccountName -Confirm:$false
(" "+ $a +" [" + $User.samaccountname + "], Removed from group [" + $_.samaccountname + "]. ") | Out-File -FilePath $ErrorLog -Append
}
}}}

PowerShell match names with user email addresses and format as mailto

So i have the below script which scans a drive for folders, it then pulls in a csv with folder names and folder owners and then matches them and outputs to HTML.
I am looking for a way to within this use PS to look up the users names in the csv grab their email address from AD and then in the output of the HTML put them as mailto code.
function name($filename, $folderowners, $directory, $output){
$server = hostname
$date = Get-Date -format "dd-MMM-yyyy HH:mm"
$a = "<style>"
$a = $a + "TABLE{border-width: 1px;border-style: solid;border-color:black;}"
$a = $a + "Table{background-color:#ffffff;border-collapse: collapse;}"
$a = $a + "TH{border-width:1px;padding:0px;border-style:solid;border-color:black;}"
$a = $a + "TR{border-width:1px;padding-left:5px;border-style:solid;border-
color:black;}"
$a = $a + "TD{border-width:1px;padding-left:5px;border-style:solid;border-color:black;}"
$a = $a + "body{ font-family:Calibri; font-size:11pt;}"
$a = $a + "</style>"
$c = " <br></br> Content"
$b = Import-Csv $folderowners
$mappings = #{}
$b | % { $mappings.Add($_.FolderName, $_.Owner) }
Get-ChildItem $directory | where {$_.PSIsContainer -eq $True} | select Name,
#{n="Owner";e={$mappings[$_.Name]}} | sort -property Name |
ConvertTo-Html -head $a -PostContent $c |
Out-File $output
}
name "gdrive" "\\server\location\gdrive.csv" "\\server\location$"
"\\server\location\gdrive.html"
Try adding something like this to the select:
#{n="email";e={"mailto:"+((Get-ADUser $mappings[$_.Name] -Properties mail).mail)}
You need to load the ActiveDirectory module before you can use the Get-ADUser cmdlet:
Import-Module ActiveDirectory
On server versions this module can be installed via Server Manager or dism. On client versions you have to install the Remote Server Administration Tools before you can add the module under "Programs and Features".
Edit: I would have expected ConvertTo-Html to automatically create clickable links from mailto:user#example.com URIs, but apparently it doesn't. Since ConvertTo-Html automatically encodes angular brackets as HTML entities and I haven't found a way to prevent that, you also can't just pre-create the property as an HTML snippet. Something like this should work, though:
ConvertTo-Html -head $a -PostContent $c | % {
$_ -replace '(mailto:)([^<]*)', '$2'
} | Out-File $output
Here's how I would do it (avoiding the use of the AD Module, only because it's not on all of my workstations and this works just the same), and assuming you know the user name already:
#Setup Connection to Active Directory
$de = [ADSI]"LDAP://example.org:389/OU=Users,dc=example,dc=org"
$sr = New-Object System.DirectoryServices.DirectorySearcher($de)
After I setup a connection to AD, I set my LDAP search filter. This takes standard ldap query syntax.
#Set Properties of Search
$sr.SearchScope = [System.DirectoryServices.SearchScope]"Subtree"
$sr.Filter = "(&(ObjectClass=user)(samaccountname=$Username))"
I then execute the search.
#Grab user's information from OU. If search returns nothing, they are not a user and the script exits.
$SearchResults = $sr.FindAll()
if($SearchResults.Count -gt 0){
$emailAddr = $SearchResults[0].Properties["mail"]
$mailto = "Contact User"
}
You can of course send the $mailto variable anywhere you want, and change it's html, but hopefully this gets you started.