Making a script at work to save some time getting approval from a Security Group owner when someone requests access to a group.
It works OK, but not great. The idea is to create a draft in Outlook2010 that is auto-filled with SG Manager Email address, Subject entered by Analyst. The body contains the requesting users Office, Dept, Manager, and Job title.
It does all this fine, but there is zero error catching, so for example, if the group name entered by the Analyst is wrong or annoyingly has a space at the end, it retains the information saved in the $managerEmail from last time you run it (I thought -like would sort this, but it does not).
Also, it only works for one user, one group, one email - where in reality there could be multiple users requesting access or a user requesting access to multiple groups. I'm looking for a hint in the right direction - would I need to employ a loop of some kind in it to accept multiple inputs and create multiple Email drafts?
It's a mash of bits of scripts together with my own extremely novice "code" so I'm sure there are parts that do nothing of value so any cleanup advise would also be appreciated, I'm not sensitive about it so any thoughts please share.
Get-Module -ListAvailable
Import-Module ActiveDirectory
#Below is user inputs required
$User = Read-Host -Prompt 'Input the user name'
$Group = Read-Host -Prompt 'Input Group Name'
$Notify = Read-Host -Prompt 'Input Notify Subject Line'
#Below is the user info
Get-ADUser -Filter {name -like $user}
if ($User -ne $null)
{
$Find = Get-ADUser $User -Server americas.cshare.net –Properties * |
Select-Object Office, Department, Name, Manager, Title
$Office = $Find.Office
$Dept = $Find.Department
$Title = $Find.Title
$Name = $Find.Name
$Usermanager = $Find.Manager
}
$UM = (get-aduser -Identity $Usermanager -Server americas.cshare.net -Properties Name);
$UserManagerName = $UM.Name;
#Below is get owner name and email need to add error catching
Get-ADGroup -Filter {name -like $Group} -Server americas.cshare.net -Properties ManagedBy |
ForEach-Object {
$managedBy = $_.ManagedBy;
if ($managedBy -ne $null)
{
$manager = (get-aduser -Identity $managedBy -Server americas.cshare.net -Properties emailAddress);
$managerName = $manager.Name;
$managerEmail = $manager.emailAddress;
}
else
{
$managerName = 'N/A';
$managerEmail = 'N/A';
}
Write-Output $_;
} |
Select-Object -Property #(
#{n = 'Group Name'; e = {$_.Name}}
#{n = 'Managed By Name'; e = {$managerName}}
#{n = 'Managed By Email'; e = {$managerEmail}}
)
#Below is the email part saves a draft to check yourself before sending
$ol = New-Object -comObject Outlook.Application
$mail = $ol.CreateItem(0)
$null = $Mail.Recipients.Add("$managerEmail")
$Mail.Subject = "$Notify"
$Mail.Body = #"
Dear $managerName,
$Name has requested to be added to the security group $Group
Job Role: $Title
Department: $Dept
Office: $Office
Manager: $UserManagerName
As the owner of $Group, can you review this request and approve/deny accordingly.
Please REPLY ALL when you respond.
Kind Regards,
Service Desk
"#
$Mail.Save()
So I took a stab at it and added validation steps, but you don't really have much cause for "error handling" exactly. Since this script depends on a lot of user input, I cleaned up that process. Also addressed your problem with group searches. The filter doesn't support -match, so I added proper wildcards on each end of the group name (to catch those various bits). Not sure what the Select-Object was for at the end of your pipeline, but that's in there, too, in the form of New-Object.
Import-Module -Name ActiveDirectory
#region User details
do {
$user = Read-Host -Prompt SID
$adArgs = #{
Identity = $user
Server = 'americas.cshare.net'
Properties = 'Office','Department','Name','Manager','Title'
ErrorAction = 'SilentlyContinue'
}
$user = Get-ADUser #adArgs
if (-not $user) {
'SID not found in Active Directory. Try again.'
}
} until ($user)
$userName = $user.Name
$userTitle = $user.Title
$userDept = $user.Department
$userOffice = $user.Office
$userManager = Get-ADUser -Identity $user.Manager -Server americas.cshare.net -Properties Name |
Select-Object -ExpandProperty Name
#endregion
#region Group details
do {
$group = Read-Host -Prompt Group
$adArgs = #{
Filter = "Name -like '*$group*'"
Server = 'americas.cshare.net'
Properties = 'ManagedBy'
ErrorAction = 'SilentlyContinue'
}
$adGroup = Get-ADGroup #adArgs
if (-not $adGroup) {
'Group not found in Active Directory. Try again.'
}
if ($adGroup.Count -gt 1) {
'Multiple groups found matching query. Try again.'
$adGroup = $null
}
} until ($adGroup)
try {
$adArgs = #{
Identity = $adGroup.ManagedBy
Server = 'americas.cshare.net'
Properties = 'emailAddress'
ErrorAction = 'Stop'
}
$groupManager = Get-ADUser #adArgs
} catch {
"Failed to retrieve '$group' manager! $_" # prints the AD error
Pause
Exit
}
$groupManagerName = $groupManager.Name
$groupManagerEmail = $groupManager.emailAddress
#endregion
$subject = Read-Host -Prompt Subject
New-Object -TypeName PSCustomObject -Property #{
'Group Name' = $group
'Managed By Name' = $groupManagerName
'Managed By Email' = $groupManagerEmail
}
#region Draft an email
$ol = New-Object -ComObject Outlook.Application
$mail = $ol.CreateItem(0)
$null = $mail.Recipients.Add($groupManagerEmail)
$mail.Subject = $subject
$mail.Body = #"
Dear $groupManagerName,
$userName has requested to be added to the security group $group
Job Role: $userTitle
Department: $userDept
Office: $userOffice
Manager: $userManager
As the owner of $group, can you review this request and approve/deny accordingly.
Please REPLY ALL when you respond.
Kind Regards,
Service Desk
"#
$mail.Save()
#endregion
Pause
Related
I am trying to add users through my AC / DC with a powershell script then put those users in their assigned groups from a csv file. This is the code I have but when I put it into powershell and run it, it doesnt do anything. I waited about 30 min and nothing populated in for the users or groups. Does it usually take a while to add users and put them in groups or is there something I am missing?
$UserList = Import-Csv -Path 'C:\Users\Administrator\Desktop names.csv'
foreach ($User in $UserLis) {
$Attributes = #{
Enabled = $true
ChangePasswordAtLogon = $true
Path = "OU=balrok Users, DC=balrok, DC=edu"
Name = "$($User.First) $($User.Last)"
UserPrincipleName = "$($User.First) $($User.Last)#balrok.edu"
SamAccountName = "$($User.First) $($User.Last)"
EmailAddress = "$($User,First)#balrok.edu"
GivenName = $User.First
Surname = $User.Last
Description = $User.Age
Group = $User.Group
AccountPassword = "1234" | ConvertTo-SecureString -AsPlainText -Force
if (Add-ADGroupMember -Identity $Group -Members $SamAccountName){
Write-Host "Added" $SamAccountName to $Group" -ForeGroundColor Green
}
}
New-ADUser #Attributes
}
Im trying to set AD Title and Department based on a set of data and I can seem to get it to accept my userdetails says object is not set to an instance of an object.
Here is the code I am using:
$list = Import-Excel -Path "\\VRIDATA\HelpDesk\Scripts\Dan\Guardian.xlsx"
foreach($User in $list)
{
$display = $User.Name
$dept = $User.Department
$title = $User.JobTitle
$UserID = Get-ADUser -Filter { Name -like $display } | Select samAccountName
$Userdetails = #{
Identity = $UserID
Title = $title
Department = $dept
}
Set-AdUser #Userdetails
}
Basically the idea of this script is to create a new user in AD but to also copy groups from another user in AD from a search with user input.
For example copy sales groups from a current team member to the newly created member. The error I'm getting is that my $ID variable is always empty and -Identity cant use it. If I hardcode the user I want to copy from this code works.
I can just ask for user input and have them put in the identity / username / samaccountname to copy groups from but they're not going to know that off the top of their head as the naming convention in AD includes employee numbers. They'd have to navigate AD to find that and this avoids the point of the script.
I want this script to be able to lookup a user based on just name for ease of use. This is why it uses -filter. If you have suggestions on how to handle potential duplicates of users with same name during this search I'm all ears for that too.
After it finds the user to copy from it copies the groups from the searched user to the newly created user.
Thanks for any help!
Do {
$Given = Read-Host -Prompt "Input new user first name"
$Surname = Read-Host -Prompt "Input new user last name"
$PW = Read-Host -Prompt "Input new user password"
$Phone = Read-Host -Prompt "Input new user phone number"
$NewSam = Read-Host -Prompt "Input preferred new user ID"
$User = "$Given $Surname"
$Confirmation = Read-Host "You input '$User' , '$NewSam' , '$PW' , and '$Phone' is this correct (y/n)?"
}
while ($confirmation -ne "y")
New-ADUser -Name $User -GivenName $Given -Surname $Surname -SamAccountName $NewSam -AccountPassword (ConvertTo-SecureString -AsPlaintext "$PW" -Force) -Enabled $True `
-OfficePhone $Phone -ChangePasswordAtLogon $true
Do {
$clone = Read-Host -Prompt "Who are we copying groups from?"
$Confirmation2 = Read-Host "You input '$clone' is this correct (y/n)?"
}
while ($confirmation2 -ne "y")
$ID = Get-ADUser -Filter 'Name -eq "$clone"'| Select-Object -ExpandProperty SamAccountName
$GetUserGroups = Get-ADUser -Identity "$ID" -Properties memberof | Select-Object -ExpandProperty memberof
$GetUserGroups | Add-ADGroupMember -Members $NewSam -Verbose
While asking for user input via Read-Host is always tricky (a user can type in any bogus text he/she wants), I would at least give that user the opportunity to quit the loop by adding the q option in there as well.
Then you really should first do a check if the user perhaps already exists or not before creating with New-ADUser.
Finally, $GetUserGroups | Add-ADGroupMember -Members $NewSam -Verbose will not work as you expect, because the -Identity parameter for Add-ADGroup only takes one single group id at a time, so you need to loop over the groups there.
Try
do {
$Given = Read-Host -Prompt "Input new user first name"
$Surname = Read-Host -Prompt "Input new user last name"
$PW = Read-Host -Prompt "Input new user password"
$Phone = Read-Host -Prompt "Input new user phone number"
$NewSam = Read-Host -Prompt "Input preferred new user ID (SamAccountName)"
$User = "$Given $Surname"
$Confirmation = Read-Host "You input '$User' , '$NewSam' , '$PW' , and '$Phone' is this correct (y/n/q)?"
switch -Wildcard ($confirmation) {
'q*' {
# user wants to quit
exit
}
'y*' {
# here first check if that user already exists or not
$existingUser = Get-ADUser -Filter "SamAccountName -eq '$NewSam'"
if ($existingUser) {
Write-Warning "A user with SamAccountName '$NewSam' already exists"
$Confirmation = 'n' # rerun the loop
}
}
}
} while ($confirmation -notlike "y*")
# now proceed creating the new AD user
# because New-ADUser can take a lot of parameters, the cvleanest way is to use splatting
$userProps = #{
Name = $User
GivenName = $Given
Surname = $Surname
SamAccountName = $NewSam
AccountPassword = ConvertTo-SecureString -AsPlaintext $PW -Force
Enabled = $True
OfficePhone = $Phone
ChangePasswordAtLogon = $true
# add switch parameter PassThru, so the cmdlet returns the new user object
PassThru = $true
}
$newUser = New-ADUser #userProps
do {
$clone = Read-Host -Prompt "Who are we copying groups from? (SamAccountName)"
$Confirmation = Read-Host "You input '$clone' is this correct (y/n/q)?"
switch -Wildcard ($Confirmation) {
'q*' {
# user wants to quit
Write-Host "New user '$($newUser.Name)' has been created but not added to any groups.."
exit
}
'y*' {
# here first check if that user already exists or not
$cloneUser = Get-ADUser -Filter "SamAccountName -eq '$clone'" -Properties MemberOf
if (!$cloneUser) {
Write-Warning "A user with SamAccountName '$clone' does not exist"
$Confirmation = 'n' # rerun the loop
}
else {
# get the MemberOf properties from the second user
# and add the new user to these groups
$cloneUser.MemberOf | ForEach-Object {
$_ | Add-ADGroupMember -Members $newUser -Verbose
}
}
}
}
}
while ($confirmation -notlike "y*")
P.S. I'm using wildcard comparisons ('y*') on the confirmation input because otherwise if a user types 'yes' the loop will not see that as a YES
Your script starts to go sideways here:
$ID = Get-ADUser -Filter 'Name -eq "$clone"'| Select-Object -ExpandProperty SamAccountName
$GetUserGroups = Get-ADUser -Identity "$ID" -Properties memberof | Select-Object -ExpandProperty memberof
And you're so close, what's needed is:
$ID = Get-ADUser -Filter "Name -eq '$clone'"|
Select-Object -ExpandProperty SamAccountName
The Filter requires single quotes around the name. The documentation on this is horrible and, for the Filter parameter, uses ScriptBlocks (code inside curly braces) in the examples while the actual type is [string]. I learned to stick with strings after fixing problems that were obscured by using ScriptBlocks.
You wouldn't even run into this problem if you simplified to:
$ID = Get-ADUser -Identity $clone |
Select-Object -ExpandProperty SamAccountName
As long as we're simplifying, you only need one line:
$GetUserGroups = Get-ADUser -Identity $clone -Properties memberof |
Select-Object -ExpandProperty memberof
One more thing to consider. While piping to Select-Object is the PowerShell way and is the style I tend to use from the command line, in scripts I personally prefer:
$GetUserGroups = (Get-ADUser -Identity $clone -Properties memberof).memberof
But this is a matter of taste (while also being faster (which only matters in long running scripts)).
I'm trying to create kind of a solution to create thousands of accounts in AD add them to specific group or for service accounts add them to specific OU. Keep a log of what was done and what the errors are.
The script ingest a csv file with the following headers.
SamAccountName,name,password,ou,domain,isAdded
$Domain = [system.directoryservices.activedirectory.domain]::GetCurrentDomain().Name
$NewUserADGroup = 'Print Operators'
$NewUsersList = Import-Csv .\bulk_user1.csv | Where-Object{$_.domain -like "$Domain"}
$NewUsersList | ForEach-Object{
$NewUserAttributes = #{
SamAccountName = $_.SamAccountName
name = $_.name
#path = $_.parentou
#UserPrincipalName = $_."samAccountName" + "#lovely.Local"
AccountPassword = (convertto-securestring "$NewUsersList.password" -AsPlainText -Force)
Enabled = $true
#Server = $dcname
#isAdded = $Issue
}
try{
#Create new User and add to specific group
New-ADUser $NewUserAttributes
Add-ADGroupMember -Identity $NewUserADGroup -Members $_.SamAccountName
#Delete Specific User
#Remove-ADUser -Identity $_.SamAccountName
}catch{
Write-Warning $_
$Issue = $_.ToString()
}
$count = $count + 1
Write-Host $_.SamAccountName " " $_.Name " " $_.SamAccountName.Enabled " Total:" $NewUsersList.Count + "Processed:" $count
$NewUserAttributes| Select-Object -Property SamAccountName,name,AccountPassword,Enabled,isAdded | Export-Csv ".\$Domain.NewAccountsCreatedStatus.csv"
}
I'm getting the following error:
WARNING: The name provided is not a properly formed account name
When I look at the variable
$NewUserAttributes
I do see the name and the value:
Name Value
---- -----
Enabled True
name bfmbsngfilexfer2
AccountPassword System.Security.SecureString
SamAccountName bfmbsngfilexfer2
As promised, below a rewrite of your code.
I have inserted comments to hopefully explain what the code does:
$Domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().Name
$NewUserADGroup = 'Print Operators'
$successCount = 0
$NewUsersList = Import-Csv .\bulk_user1.csv | Where-Object { $_.domain -eq $Domain } | ForEach-Object {
# capture the human readable password for output use
$password = $_.password
$userParams = #{
SamAccountName = $_.SamAccountName
Name = $_.name
Path = $_.parentou
UserPrincipalName = '{0}#lovely.Local' -f $_.SamAccountName
AccountPassword = ConvertTo-SecureString $_.password -AsPlainText -Force
Enabled = $true
#Server = $dcname
}
try{
# Create new User and add to specific group
$user = New-ADUser #userParams -PassThru -ErrorAction Stop
Add-ADGroupMember -Identity $NewUserADGroup -Members $user -ErrorAction Stop
# add the 'isAdded' element to the $userParams hashtable
$userParams['isAdded'] = $true
$successCount++
}
catch{
Write-Warning $_.Exception.Message
$userParams['isAdded'] = $false
}
# output a PsCustomObject with values taken from the Hashtable
# AccountPassword is a SecureString, which will be of no use to you..
# Output the human readable password instead so you can inform the new users.
[PsCustomObject]$userParams | Select-Object SamAccountName, Name,
#{Name = 'Password'; Expression = {$password}},
Enabled, isAdded
}
# output
# use '#($NewUsersList)' to force it as array, so the Count property is accurate
if (#($NewUsersList).Count) {
Write-Host ('Processed: {0} Succeeded: {1}' -f $NewUsersList.Count, $successCount) -ForegroundColor Green
$NewUsersList | Export-Csv ".\$Domain.NewAccountsCreatedStatus.csv" -NoTypeInformation
}
else {
Write-Host 'No users successfully processed!' -ForegroundColor Red
}
im currently working on a automated User Attributes updating scrips, and currently that working fine, i am having issues however collecting the list of changes so they can be bundled inot an email notification for the administrator
here is what i have so far,
i want to form a list of all changes so that i can add that to an send-mailmessage to an admin for each user, but only when there is a change.
at the moment i only get whatever the latest thing changed is, not a list.
$csvFile = 'C:path.csv' # Enter a path to your import CSV file
$validUsernameFormat = '[^a-zA-Z_.]' # identifies anything that's _not_ a-z or underscore or .
$Mailpassword = ConvertTo-SecureString -string “4a1fd5e9f7e26f” -AsPlainText -Force
$MailCred = New-Object System.Management.Automation.PSCredential -argumentlist "38da1ca9daf082", $Mailpassword
$mailBody = $NewUserParams | out-string
# read the input csv and loop through
Import-Csv -Path $csvFile | ForEach-Object {
$firstName = $_.FirstName.Trim()
$surname = $_.Surname.Trim()
#$validUsernameFormat = "[^a-zA-Z_.]" # identifies anything that's _not_ a-z or underscore or .
$vaildusername = "($firstName'.'$surname)" -replace $validUsernameFormat, '' #removes anything that isn'tin $validUsernameFormat
$truncateifgreaterthanXchar = '(?<=^.{20}).*'
$username = $vaildusername -replace $truncateifgreaterthanXchar
$DefaultPassword = 'Pa$$w0rd'
$securePassword = ConvertTo-SecureString -String $DefaultPassword -AsPlainText -Force
# test if a user with that name already exists
$user = Get-ADUser -Filter "SamAccountName -eq '$username'" -ErrorAction SilentlyContinue
if ($user) {
$CurrentAttributes = Get-ADUser -Identity $username -Properties *
# You don't need this line because you are already declaring the variable in the next one
# [psobject]$CorrectAttributes
$CorrectAttributes = #{
SamAccountName = $username
Name = "$firstname $surname"
DisplayName = "$firstname $surname"
UserPrincipalName = "$username#domain.com"
GivenName = $firstname
Surname = $surname
Path = "CN=Users,DC=domain,DC=com" #change to switch based of Users Branch
City = $_.City
Country = $_.Country #NOTE: This Feild must be the 2 digit Country Code, NOT the String Name of athe Country.
department = $_.OrgDepartmentName
Employeeid = $_.EmployeeId
mobile = $_.Mobile
Manager = $_.Manager
Office = $_.Branch
postalCode = $_.PostalCode
POBox = $_.PostOfficeBox
scriptPath = $_.scriptPath
Street = $_.StreetName
Title = $_.Title
}
[System.Collections.ArrayList]$MailAttributesList = #()
foreach ($attribute in $CorrectAttributes.Keys) {
if ($currentAttributes.$attribute -ne $correctAttributes.$attribute) {
$params = #{Identity = $username; $attribute = $correctAttributes.$attribute }
$mailUpdatedAttribute = $CorrectAttributes.$attribute | Out-String
Set-ADUser #params
[void]$MailAttributesList.add("$attribute")
}
else {
Write-Host "$username '$attribute' is correct"
}
$MailAttributesList
$MailAttributesList = #()
}
}
Taken from the part where you test if the user exists and then check which attributes need to be updated, I'd do this:
# personally, I hate using -Properties *
# better to list the properties you are trying to update:
$userAttribs = 'SamAccountName','Name','DisplayName','UserPrincipalName',
'GivenName','Surname','Path','City','Country','Department',
'EmployeeId','MobilePhone','Manager','Office','PostalCode',
'POBox','ScriptPath','Street','Title'
# test if a user with that name already exists
$user = Get-ADUser -Filter "SamAccountName -eq '$username'" -Properties $userAttribs -ErrorAction SilentlyContinue
if ($user) {
# always https://learn.microsoft.com/en-us/powershell/module/addsadministration/set-aduser
# for the correct parameter names!
$CorrectAttributes = #{
SamAccountName = $username
Name = "$firstname $surname"
DisplayName = "$firstname $surname"
UserPrincipalName = "$username#domain.com"
GivenName = $firstname
Surname = $surname
Path = "CN=Users,DC=domain,DC=com" #change to switch based of Users Branch
City = $_.City
Country = $_.Country #NOTE: This Feild must be the 2 digit Country Code, NOT the String Name of athe Country.
Department = $_.OrgDepartmentName
EmployeeId = $_.EmployeeId
MobilePhone = $_.Mobile
Manager = $_.Manager # must be a DistinguishedName, GUID, SID or SamAccountName
Office = $_.Branch
PostalCode = $_.PostalCode
POBox = $_.PostOfficeBox
ScriptPath = $_.scriptPath
Street = $_.StreetName
Title = $_.Title
}
# create a new empty hashtable to store the properties that need updating
$UpdateAttribs = #{}
# capture all changes on behalf of the email body
$changes = foreach ($prop in $CorrectAttributes.Keys) {
if ($user.$prop -ne $CorrectAttributes[$prop]) {
# output an object with the property to be changed for the email
[PsCustomObject]#{
Property = $prop
OldValue = $user.$prop
NewValue = $CorrectAttributes[$prop]
}
# add the value to update in the the $UpdateAttribs hashtable
$UpdateAttribs[$prop] = $CorrectAttributes[$prop]
}
}
if ($changes) { # or do: if ($UpdateAttribs.Count)
# perform the changes
$user | Set-ADUser #UpdateAttribs
# send the email to the admin
$mailParams = #{
To = 'admin#yourcompany.com'
From = 'attributeChanger#yourcompany.com'
Subject = 'User attributes changed'
Body = "Changed AD attributes for user $username`r`n" + ($changes | Format-Table -AutoSize | Out-String)
SmtpServer = 'smtp.yourcompany.com'
UseSsl = $true
Credential = $MailCred
# maybe more parameters go here..
}
Send-MailMessage #mailParams
}
else {
Write-Host "All attributes for user '$username' are correct"
}
}
else {
Write-Warning "User $username does not exist"
}
P.S. The code could be shortened if you have your input CSV file use the correct headers, so they match the property names for Set-ADUser exactly..