I am automating the new user account process from Cherwell to AD, I am stuck with duplicate samaccountname. I have successfully gotten accounts to create if the samaccountname does not already exist. I have a check in place that for now just writes the output to say whether the name is found or not.
Tinker Bell is my test user as she is my daughters favourite character.
$FirstName = “tinker”
$SurName = “bell”
for ($i = 1; $i -le $SurName.Length; ++$i) {
$Account = $null;
$Identity = $FirstName + $SurName.Substring(0,$i)
$Account = Get-ADUser -Filter {sAMAccountName -eq $Identity}
if ($Account -eq $null) {"User does not exist in AD"}
Else {"User found in AD"}
write-Output $Identity
}
I have this in place which does what it should do, I am just not sure how to stop it when the next samaccountname is created.
This is what I get as output
User found in AD
tinkerb
User does not exist in AD
tinkerbe
User does not exist in AD
tinkerbel
User does not exist in AD
tinkerbell
So it does the first part correctly, finds the account and then moves on to the next one, but then it just loops and I am not sure how to drop it out to have a variable called $newSam created when in this example 'tinkerbe' is determined.
S.
Turn your code into a simple function that returns the first available user name:
function New-Username
{
param(
[string]$FirstName,
[string]$SurName
)
for ($i = 1; $i -le $SurName.Length; ++$i) {
$Account = $null;
$Identity = $FirstName + $SurName.Substring(0,$i)
$Account = Get-ADUser -Filter {sAMAccountName -eq $Identity}
if ($Account -eq $null) {
return $Identity
}
else {
Write-Warning "User found in AD: $Identity"
}
}
throw "Unable to generate distinct username"
}
and the use it like:
$NewUsername = New-Username -FirstName tinker -SurName bell
New-ADUser -SAMAccountName $NewUserName
Related
I need a list of users with the logonhours set to denied. I found another question (68558481) that partially got me there but I seem to be doing something wrong when trying to loop through my group.
$userToFind = Get-ADGroupMember -identity "AD group" | Sort-Object samAccountName | Select SamAccountName
# try and find the user also gathering its LogonHours property
Foreach ($username in $userToFind){
$user = Get-ADUser -Filter "SamAccountName -eq '$username'" -Properties LogonHours -ErrorAction SilentlyContinue
if ($user) {
# test if the property has been set. If not set, the user is allowed to login anytime of the week
if ($null -ne $user.LogonHours -and #($user.LogonHours).Count) {
# initialize a variable
$loginAllowed = $false
# loop through the 21 bytes and exit the loop if we found a non-zero byte
foreach ($byte in $user.LogonHours) {
if ($byte -ne 0) {
$loginAllowed = $true # set the flag to $true
break # and exit the loop
}
}
if (!$loginAllowed) {
"User $username - Login disabled"
}
}
}
else {
Write-Warning "User $username does not exist.."
}
}
My results for this is all users do not exist. Where am I going wrong?
|
The main issue is that $userToFind is an object or array of objects which's property is SamAccountName, so when you use it in your filter "SamAccountName -eq '$username'", the result is the stringification of each object:
$username = [pscustomobject]#{
samAccountName = 'someuser'
}
"SamAccountName -eq '$username'" # => SamAccountName -eq '#{samAccountName=someuser}'
What you want to do instead is get the value of each object, a string or array of strings in this case:
$username = [pscustomobject]#{
samAccountName = 'someuser'
} | Select-Object -ExpandProperty samAccountName
"SamAccountName -eq '$username'" # => SamAccountName -eq 'someuser'
You can also use member-access enumeration instead of Select-Object, so in your code it would be:
$userToFind = (Get-ADGroupMember -Identity "AD group" | Sort-Object samAccountName).SamAccountName
As for the current code, you could make an improvement by only searching for users member of the group which's logonHours attribute is populated. That way, there is no need to check if the user has the attribute set and no need to check if the user was found or not.
# get the DN of the group
$group = (Get-ADGroup -Identity "AD group").DistinguishedName
$param = #{
LDAPFilter = "(&(memberof=$group)(logonhours=*))"
Properties = "logonHours"
}
# enumerate all users members of this group which's attribute `LogonHours` is set
foreach($user in Get-ADUser #param) {
$loginAllowed = $false
foreach($byte in $user.LogonHours) {
if ($byte -ne 0) {
$loginAllowed = $true
break
}
}
if (-not $loginAllowed) {
"User {0} - Login disabled" -f $user.SamAccountName
}
}
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)).
Looking to get some help with my powershell script. Basically have a script that I use to bulk edit fields in Azure AD for multiple users and it works fine. I tried to use it for editing custom attributes for multiple users via Exchange Online and it is not working. I'm guessing it does not work the same for EO. The goal here is to pull a csv that has 2 columns (the users emails address "userprincipalname", and one column for the value I want to add for "customattribute1") Any help is appreciated.
# Connect to ExchangeOnline
Connect-ExchangeOnline
# Get CSV content
$CSVrecords = Import-Csv C:\pathtofile.csv
# Create arrays for skipped and failed users
$SkippedUsers = #()
$FailedUsers = #()
# Loop trough CSV records
foreach ($CSVrecord in $CSVrecords) {
$upn = $CSVrecord.UserPrincipalName
$user = Get-Mailbox -Filter "userPrincipalName eq '$upn'"
if ($user) {
try{
$user | Set-Mailbox -customattribute1 $CSVrecord.customattribute1
} catch {
$FailedUsers += $upn
Write-Warning "$upn user found, but FAILED to update."
}
}
else {
Write-Warning "$upn not found, skipped"
$SkippedUsers += $upn
}
}
I think there could be 2 points that leads fail to set customattribute1.
The filter expression should be : "userPrincipalName -eq '$upn'"
Seems I can't find the -Delimiter param while you import your .CSV file which will lead to unbale to pull column value correctly.
Try the code below that works perfectly for me:
Connect-ExchangeOnline
$SkippedUsers = #()
$FailedUsers = #()
$CSVrecords = Import-Csv "C:\Users\Administrator\Desktop\test.csv" -Delimiter ","
foreach($CSVrecord in $CSVrecords ){
$upn = $CSVrecord.UserPrincipalName
$user = Get-Mailbox -Filter "userPrincipalName -eq '$upn'"
if ($user) {
try{
$user | Set-Mailbox -customattribute1 $CSVrecord.customattribute1
} catch {
$FailedUsers += $upn
Write-Warning "$upn user found, but FAILED to update."
}
}
else {
Write-Warning "$upn not found, skipped"
$SkippedUsers += $upn
}
}
My test .csv file:
After run the PS command, try to get customattribute1 of my test user:
Let me know if you have more questions.
Need to write a script to take an input file (text) list of names, check if it exists in AD, and create new computers.
The requirements are as follows -
Computer names are based on the users name (input from file)
Names must be 15 characters (for name resolution)
if the truncated name doesnt exist, create a computer object in specific OU with the truncated name.
If the truncated name does exist, append -# and test to see if it exists until it finds one that doesnt, then create new computer object with that name.
At the end I will need to output the results to an array but I haven't started adding that yet since this doesn't work.
So I finally got the "else" part but the if part at the beginning does not work.
$users = get-content C:\scriptdata\VMS.txt
$OU = ************
foreach ($user in $users)
{
$cleanname = if ($user.Length -gt 15) { $user.Substring(0, 15) } else { $user }
$exist = (get-adcomputer $cleanname) -eq $null
if ((get-adcomputer $cleanname) -like "get-adcomputer : Cannot find an object with identity")
{
New-ADComputer -Name $cleanname -Path "$OU" -SAMAccountName $cleanname -confirm
}
else
{
$count=0
DO{
$count++
$cleanname13 = if ($user.Length -gt 13) { $user.Substring(0, 13) } else { $cleanname }
$cleannamedash = $cleanname13 + '-' + "$count"
}
UNTIL ((get-adcomputer $cleannamedash | out-null) -eq $null)
New-ADComputer -Name $cleannamedash -Path "$OU" -SAMAccountName $cleannamedash -confirm
}
}
currently works for -# but not for those that dont exist at all.
Have a look at Naming conventions in Active Directory for computers, domains, sites, and OUs.
You'll find that there is more to a valid computer name than just the length.
Mind that the New-ADComputer cmdlet creates a new computer object, but does not join a computer to a domain.
Something like this should work (untested)
$computers = Get-Content C:\scriptdata\VMS.txt | Where-Object { $_ -match '\S'}
$OU = ************
foreach ($name in $computers) {
$newName = ($name -creplace '[\\/:*?"<>|.]','').Substring(0, 15)
try {
$computer = Get-ADComputer -Filter "Name -eq '$newName'" -PassThru -ErrorAction Stop
}
catch {
$computer = $null
}
if ($computer) {
# a computer with that name already exists, create a new name by adding a dash and two digit number
$count = 0
$name12 = $newName.Substring(0, 12) # we're going to add three characters
# get an array of computernames that are like the one you want to create
$existingComputers = Get-ADComputer -Filter "Name -like '$name12-*'" | Select-Object -ExpandProperty Name
do {
$newName = '{0}-{1:00}' -f $name12, ++$count
}
until ($existingComputers -notcontains $newName -or $count -gt 99)
if ($count -gt 99) {
$newName = '{0}-XX' -f $name12
throw "Cannot create computer $newName because all index numbers 00..99 are taken.."
}
}
# use splatting, because New-ADComputer has MANY parameters
$props = #{
'Name' = $newName
'Path' = $OU
'SamAccountName' = $newName
'Enabled' = $true
'Confirm' = $true
}
Write-Host "Creating computer '$newName'"
New-ADComputer #props
}
I assume you mean that this is the line that's not working:
if ((get-adcomputer $cleanname) -like "get-adcomputer : Cannot find an object with identity")
And even this doesn't work:
$exist = (get-adcomputer $cleanname) -eq $null
The reason is the same in both cases: If the computer doesn't exist, then Get-ADComputer throws an exception and the comparison is never done.
There is a good article about this here, but in short, the solution is to catch the exception. For you, it would look something like this:
try {
$computer = Get-ADComputer $cleanname
# If we get here, we know it exists
# You can put your loop here and just keep looping until Get-ADComputer throws an exception
}
catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException]
{
# Whatever you last tried doesn't exist. Create it here.
}
I'm trying to write a powershell script that accepts an username as an argument, and displays the last logon time of the user. If the user has not logged in before, the message has not logged in before should be displayed.
For example, if you run .\lastlogon -username marywong the message is displayed:
marywong last logon time 13/07/2017
If you run .\lastlogon -username guest, I get the message:
guest has not logged in before
Below is my code, however it doesn't seem to be looping into the else loop when the user has not logged in before.
param (
[string]$username
)
$user = Get-ADUser -Filter {sAMAccountName -eq $username} | Get-ADObject -Properties lastLogon
$lastlogontime = $user.lastlogon
If ($user -ne $Null) {
if($user.LastLogon -gt $time) {
$displaylastlogon = [datetime]::FromFileTime($lastlogontime)
Write-Host $username " last logon time" $displaylastlogon
}
else {
$displaylastlogon = [datetime]::FromFileTime($lastlogontime)
Write-Host $username " has not logged in before"
}
}
else {
Write-Host $username " does not exist"
}
There is information to be gained from using Get-ADUser and Get-ADObject separately. If the user has never logged in, they are still a user that exists. That is different from a user that does not exist.
[CmdletBinding()]
Param (
[Parameter(Mandatory = $true, Position = 0)]
[string]$username
)
$user = Get-ADUser -Filter {SamAccountName -eq $username}
if ($user -ne $null) {
$userlogon = $user | Get-ADObject -Properties lastLogon
if ($userlogon.LastLogon -ne $null) {
$lastlogontime = [DateTime]::FromFileTime($userlogon.LastLogon)
Write-Host $username " last logon time" $lastlogontime
} else {
Write-Host $username " has not logged in before"
}
} else {
Write-Host $username " does not exist"
}
When you use the lastLogon you get a format that AD uses...
then when the if is running you get
Could not compare "131820853335016078" to "09/24/2018 18:18:57". Error: "Cannot convert value "9/24/2018 6:18:57 PM" to type "System.Int64". Error: "Invalid cast from 'DateTime' to 'Int64'.""
so it's not getting to the else..
try using the LastLogonDate property, it will help you more.
try to use this:
$user = Get-ADUser -Filter {sAMAccountName -eq $username} -Properties LastLogonDate
$lastlogontime = $user.lastlogonDate
Edit:
you have some more issues with you code:
You need to remove the displaylastlogon
you cant use -gt because it will always be false.. the user cant log in the future.. you need to use -lt
here is the full script, working:
$user = Get-ADUser -Filter {sAMAccountName -eq $username} -Properties LastLogonDate
$lastlogontime = $user.lastlogonDate
If ($user -ne $Null) {
if($lastlogontime -lt $time)
{
Write-Host $username " last logon time" $lastlogontime
}
else
{
Write-Host $username " has not logged in before"
}
}
else
{
Write-Host $username " does not exist"
}
Another Edit:
I just notice that its not answering the case when user never logon, because you will get $null and $null is lower then the current Time. so you need to check that the lastlogontime is not null
change the if to this:
if($lastlogontime -ne $null -and $lastlogontime -lt $time)