Powershell & Get-ADUser - Split OU getting the 3rd, 4th and 5th elements from the end - powershell

Running the following in Powershell I get the result below.
Get-ADUser -Filter * -Properties DistinguishedName | select DistinguishedName | Select-Object -Property #{label='Result';expression={$_.DistinguishedName}}
Result
CN=User Full Name 1,OU=AMS,OU=DS,DC=domain,DC=local
CN=User Full Name 2,OU=TECHM,OU=DS,DC=domain,DC=local
CN=User Full Name 3,OU=Agencies,OU=HK,OU=Developers,DC=domain,DC=local
CN=User Full Name 4,OU=Agencies,OU=CH,OU=Developers,DC=domain,DC=local
CN=User Full Name 5,OU=Agencies,OU=US,OU=Developers,DC=domain,DC=local
CN=User Full Name 6,OU=Market,OU=PP,OU=Developers,DC=domain,DC=local
CN=User Full Name 7,OU=Market,OU=HK,OU=Developers,DC=domain,DC=local
This is my OU in Active Directory
DS
AMS
TECHM
Developers
CH
Agencies
Market
HK
Agencies
Market
I need a way to adapt this query (doing a split or whatever) so I am able to obtain the third item (comma separated) - most important - from the end and 4th and 5th. This is the output I'd like to get:
OU Where Type
--------------------------------
DS AMS N/A
DS TECHM N/A
Developers CH Agencies
Developers CH Market
Developers HK Agencies
Developers HK Market
I found some examples to split strings but nothing to accomplish what I am trying here.
Any ideas?!
Any help is greatly appreciated. Thanks in advance!

Given the values you're wanting to extract, I'd start with CanonicalName, rather than DistinguishedName:
Get-ADUser -Filter * -Properties CanonicalName |
select -ExpandProperty CanonicalName |
ForEach-Object {
$Parts = $_.split('/')
$Object =
[PSCustomObject]#{
OU = $Parts[1]
Where = $Parts[2]
Type = 'N/A'
}
if ($Parts.count -ge 5)
{ $Object.Type = $Parts[3] }
$Object
}

You are not interested in ",DC=domain,DC=local", so you might as well remove them by either -replace ",DC=domain,DC=local","" or by using substring method of string.
You really did not need that last select-object, nor specifying DistinguishedName, as by default you would get that with get-aduser
For example
$users = (get-aduser -filter *).distinguishedname -replace ",DC=domain,DC=local",""
Then, you split the results with "," to a temporary array variable and get the ones you like, starting from the last one using array index [-1]
$users | foreach {$where = $type = $OU = ""}{
$temp=#($_ -split ",")
if ($temp.count -eq 1) {
$OU = $temp[0].substring(3) # substring to remove the first 3 chars, that is OU= or CN=
} elseif ($temp.count -eq 2) {
$where = $temp[1].substring(3)
$OU = $temp[0].substring(3)
} else {
$type = $temp[-1].substring(3)
$where = $temp[-2].substring(3)
$ou = $temp[-3].substring(3)
}
# Finally create a custom obhect out of these
[PSCustomObject]#{
Type=$Type
Where=$where
OU=$OU
}
}
I am typing this on top of my head, you should test but I guess you get the idea..

Related

Exporting last logon date for inactive users via PowerShell

I have a command that will export a list of users who have logged in for 12 months but I am struggling to export the last login date and time.
The command is as follows:
Search-ADAccount –AccountInActive -UsersOnly –TimeSpan 365:00:00:00 –ResultPageSize 2000 –ResultSetSize $null |?{$_.Enabled –eq $True} | Select-Object Name, SamAccountName, DistinguishedName, lastLogon| Export-CSV “C:\Users\Me\Desktop\InactiveUsers.CSV” –NoTypeInformation
But lastLogon is showing a blank in the CSV file.
I am new to PowerShell I understand the command can be made much smoother.
Any help on this is much appreciated.
Search-ADAccount doesn't have an option to pull other attributes from the AD Objects than the default ones, you can use Get-ADUser with an elaborate filter to query the users who haven't logged on for the past year. One option is to query the user's lastLogonTimeStamp attribute however by doing so you're risking not getting accurate results because this attribute is not replicated in real time. To get accurate one must query the user's lastLogon attribute but, since this attribute is not replicated across the Domain, one must query all Domain Controllers to get the latest logon from the user.
For more information on this topic, please check this excellent TechNet Article: Understanding the AD Account attributes - LastLogon, LastLogonTimeStamp and LastLogonDate.
$dateLimit = [datetime]::UtcNow.AddYears(-1).ToFileTimeUtc()
$AllDCs = Get-ADDomainController -Filter *
$logons = #{}
$params = #{
LDAPFilter = -join #(
"(&" # AND, all conditions must be met
"(!samAccountName=krbtgt)" # exclude krbtgt from this query
"(!samAccountName=Guest)" # exclude Guest from this query
"(userAccountControl:1.2.840.113556.1.4.803:=2)" # object is Disabled
"(lastLogon<=$dateLimit)" # lastLogon is below the limit
")" # close AND clause
)
Properties = 'lastLogon'
}
foreach($DC in $AllDCs) {
$params['Server'] = $DC
foreach($user in Get-ADUser #params) {
# this condition is always met on first loop iteration due to ldap filtering condition
if($logons[$user.samAccountName].LastLogon -lt $user.LastLogon) {
$logons[$user.samAccountName] = $user
}
}
}
$logons.Values | ForEach-Object {
[PSCustomObject]#{
Name = $_.Name
SamAccountName = $_.SamAccountName
DistinguishedName = $_.DistinguishedName
lastLogon = [datetime]::FromFileTimeUtc($_.lastLogon).ToString('u')
}
} | Export-CSV "C:\Users\Me\Desktop\InactiveUsers.CSV" -NoTypeInformation

Function to generate samaccountname in PowerShell

I am trying to write a PS function that can generate SamAccountName in a specific way, I have found another question here but it is not similar to what i'm trying to do.
I need the SamAccountName to be generated like this:
UNIT + initial_of_firstName + initial_of_lastName
for example employee Jane Doe in unit FLN could have FLNjd as SamAccountName, and the function should check if that ID is taken by another user and if true then SamAccountName should be:
UNIT + initial_of_firstName + first_two_initials_of_lastName such as FLNjdo
and if that is also taken then SamAccountName should be:
UNIT + first_two_initials_of_firstName + first_two_initials_of_lastName such as FLNjado
and if that is also taken then SamAccountName should be:
UNIT + first_two_initials_of_firstName + first_two_initials_of_lastName + 1 such as FLNjado1
and from here it starts adding numbers 2, 3, 4.... as long as the function finds that the ID exists.
I only managed to extract the initials needed:
$first_of_fname = ($_.Fname).Substring(0,1).ToLower()
$first_of_lname = ($_.Lname).Substring(0,1).ToLower()
$FirstandSecond_of_fname = ($_.Fname).Substring(0,2).ToLower()
$FirstandSecond_of_lname = ($_.Lname).Substring(0,2).ToLower()
I need now to know how to generate the the SamAccountName in the above form and order.
ANy help will be very much appreciated.
You can do the following:
Function Get-SamAccountName {
Param(
[Parameter(Position=0,ValueFromPipelineByPropertyName)]
$Unit,
[Parameter(Position=1,ValueFromPipelineByPropertyName)]
$First,
[Parameter(Position=2,ValueFromPipelineByPropertyName)]
$Last
)
$fcount = 1
$lcount = 1
$inc = 1
$Sam = "{0}{1}{2}" -f $unit,$first.substring(0,$fcount).ToLower(),$last.substring(0,$lcount++).ToLower()
while (Get-AdUser -Filter "SamAccountName -eq '$Sam'") {
if ($fcount -le 2) {
$Sam = "{0}{1}{2}" -f $unit,$first.substring(0,$fcount++).ToLower(),$last.substring(0,$lcount).ToLower()
} else {
$Sam = "{0}{1}{2}{3}" -f $unit,$first.substring(0,2).ToLower(),$last.substring(0,2).ToLower(),$inc++
}
}
$Sam
}
Usage Option 1: Using Named Parameters
Get-SamAccountName -Unit 'FLN' -First 'Sam' -Last 'Wise'
Usage Option 2: Using positional parameters (unnamed)
Get-SamAccountName 'FLN' 'Sam' 'Wise'
Usage Option 3: Having A Text List Of Units and Names
$users = Get-Content users.txt
$users
FLN,Sam,Wise
HRS,John,Doe
COM,Jane,Doe
$users | ConvertFrom-Csv -Header 'Unit','First','Last' | Foreach {
$_ | Get-SamAccountName
}
The pipe to ConvertFrom-Csv can be skipped here if your file is already a CSV with the proper headers.
Explanation:
The function returns a string that represents an available SamAccountName value.
$fcount is the first name character count that we want to retrieve. $lcount is the last name character count that we want to retrieve. $inc is the number we want to append to the username if needed.
$var++ notation increments the $var value by one after its line is executed. The timing of $fcount++ and $lcount++ allows us to retrieve (1,1),(1,2),(2,2) characters from (first,last) while minimizing code. $inc++ will increment after the current $inc value is retrieved.
-f is the string format operator.
The while loop will only execute if the current created SamAccountName value is found.
Note: The code shortening complexity may or may not be worth it. It does not gain much from a performance perspective, but I do feel code complexity on this level is merely relative.
To take a different approach, instead of multiple if statements, you can use the switch statement "backwards" to find out what is $null or not available, and fall back to the default switch which is the final while iteration loop incrementing the count.
#Get substrings
$first_of_fname = ($_.Fname).Substring(0,1).ToLower()
$first_of_lname = ($_.Lname).Substring(0,1).ToLower()
$FirstandSecond_of_fname = ($_.Fname).Substring(0,2).ToLower()
$FirstandSecond_of_lname = ($_.Lname).Substring(0,2).ToLower()
#create patters to match on
$pattern1 = $UNIT + $first_of_fname + $first_of_lname
$pattern2 = $UNIT + $first_of_fname + $FirstandSecond_of_lname
$pattern3 = $UNIT + $FirstandSecond_of_fname + $FirstandSecond_of_lname
#evaluate what is available
switch($null){
(Get-ADUser -Filter "samAccountName -like '$pattern1'"){
Write-Host "$pattern1 - Free"
break
}
(Get-ADUser -Filter "samAccountName -like '$pattern2'"){
Write-Host "$pattern2 - Free"
break
}
(Get-ADUser -Filter "samAccountName -like '$pattern3'"){
Write-Host "$pattern3 - Free"
break
}
default {
$cnt = 1
while (Get-ADUser -Filter "SamAccountName -like '$pattern3$cnt'") {
$cnt++
}
Write-Host "$pattern3$cnt - Free"
break
}
}

How to look for Active Directory group name from csv in PowerShell?

If I have a .csv:
ClientCode,GroupCode
1234,ABC
1234,DEF
1235,ABC
1236,ABC
and I want to get a hashtable with ClientCode as key, and values to be all AD groups with ClientCode in it, for example:
ClientCode GroupCode
---------- ---------
1234 ClientGroup_InAD_1234, some_other_client_1234
1235 ClientGroup_InAD_1235, some_other_client_in_AD_1235
1236 ClientGroup_InAD_1236
How do I go about this?
Essentially, I have client groups in Active Directory and each client has a code which is the same as the 'ClientCode' in the csv. For example, I might have a client called 'Bob' which I have assigned a code '1234' to it. Therefore, the Group for Bob in the AD would be 'Bob_1234'. Essentially I want to be able to search for whatever groups have ClientCode in them. So i want to search for the all the AD groups that have '1234'. This would return 'Bob_1234' and whatever group in the AD also has '1234' in its name.
So far I have tried:
$clientTable = #{}
foreach($rec in $csv_data) {
$groups = #(get-adgroup -Filter "name -like '*$($rec.clientcode)_*'")
write-host "Found $($groups.count) group(s) for: $($rec.clientcode)"
$clientTable[$ClientCode] = #($groups)
}
$clientTable
but I'm not getting my desired output
You can use the loop like this. You will need to search with a * at the beginning of the name you are looking to find via the Filter.
foreach($rec in $csv) {
$clientCode = "*_$($rec.ClientCode)"
if (!($clientTable.ContainsKey($clientCode))) {
$names = Get-ADGroup -Filter 'Name -like $clientCode' | select Name
$clientTable[$clientCode] = $names -join ","
}
}
This will also check for any client IDs that have already been checked and ignore those.
If you want a hash table populated with the ClientCode value as the key name, you can do the following:
$clientTable = #{}
foreach($rec in $csv_data){
$groups = #(Get-ADGroup -Filter "Name -like '*_$($rec.ClientCode)'" | Select -Expand Name)
write-host "Found $($groups.count) group(s) for: $($rec.ClientCode)"
$clientTable[$rec.ClientCode] = $groups
}
$clientTable
Keep in mind here that each value in the hash table is an array of group names. If you want a single string with comma-delimited names, you can do $clientTable[$rec.ClientCode] = $groups -join "," instead.
You will need to de-duplicate the ClientCodes in the CSV before retrieving the groups.
Something like below should do it (assuming the ClientCode is always preceded by an underscore like in _1234 as shown in your examples)
$csv = Import-Csv -Path 'ClientGroupCodes.csv'
$clientTable = #{}
$csv | Select-Object -ExpandProperty ClientCode -Unique | ForEach-Object {
# $_ is a single clientcode in each iteration (string)
# get an array of goup names
$groups = #(Get-ADGroup -Filter "Name -like '*_$_'" | Select-Object -ExpandProperty Name)
Write-Host "Found $($groups.Count) group(s) for code : '$_'"
$clientTable[$_] = $groups -join ', '
}
$clientTable

Using PowerShell to generate a unique username in AD

I'm writing a script to be used by help desk staff to quickly (and accurately) create AD user accounts by inputting basic employee information. We use portions of a persons name in order to create the samAccountName. Which I've achieve by the following.
$GivenName = "Joe"
$Surname = "Smith"
$SamAccountName = $Surname.substring(0, [System.Math]::Min(5, $Surname.Length)) + $GivenName.Substring(0,1)
This works great as long as there isn't already a "Joe Smith" (or John or Jennifer Smithers, etc). The solution would be to add a number to the end. When manually creating accounts the help desk would search AD look at what number suffix to use if necessary. I'm trying to figure out how PowerShell can do that for us. I've gone through several ideas with help from what I've found online but so far I've been unsuccessful.
My first thought was to do something like this.
$SamSuffix = 2
If ((Get-ADUser -LDAPFilter "(SamAccountName=$samAccountName)")-eq $Null)
{
"$SamAccountName does not exist in AD" #out result for testing.
}
Else{
do
{
Get-ADUser -LDAPFilter "(SamAccountName=$SamAccountName + $SamSuffix++)"
}
until (Get-ADUser -LDAPFilter "(SamAccountName=$SamAccountName + $SamSuffix)")-eq $Null)
}
This obviously doesn't work. Even if it did I don't know how I'd get to the 'next step' to create the account.
I also tried pulling the existing names into a list
$SamExist = (Get-ADUser -LDAPFilter "(SamAccountName=$SamAccountName*)" | Select SamAccountName)
do {$SamAccountName + $SamSuffix++}
until ($SamExist -notcontains $SamAccountName -or $SamAccountName + $SamSuffix)
This also doesn't work but if it did I can see that it would automatically add the suffix even if it wasn't needed.
You approach where you get all the existing matches first would be where I would start. Lets assume $SamAccountName is smithj
$existingAccounts = Get-ADUser -Filter "samaccountname -like '$SamAccountName*'" -ErrorAction SilentlyContinue | Select-Object -ExpandProperty samaccountname
So $existingaccounts that have a samaccountname's starting with smithj. If there are not any then $existingAccounts would be null and we can test for that.
if($existingAccounts){
# Figure out what the suffix will be
$highestValue = $existingAccounts -replace "^$SamAccountName" |
ForEach-Object{[int]$_} |
Measure-Object -Maximum |
Select-Object -ExpandProperty Maximum
} else {
# Create the user as normal.
}
Pretending there are some accounts that exist we trim the leading characters from the samaccountname, convert the remaining to an integer and select the highest from that. So $highestValue is the last number used in a conflicting account.
Add one to that and you have a guaranteed username you can create assuming nothing changes in those moments i.e. two users making to smithj accounts.
If you are looking to fill gaps where a user might have left and you want to use the next available after 1 then you could do this.
$existingAccounts = "smithj1", "smithj5", "smithj10", "smithj2", "smithj3"
# Figure out what the next unused suffix will be
$existingSuffixes = $existingAccounts -replace "^$SamAccountName" | ForEach-Object{[int]$_}
# Once again determine the maximum one in use
$highestValue = $existingSuffixes | Measure-Object -Maximum | Select-Object -ExpandProperty Maximum
# Find the first gap between 1 and the max suffix
$nextAvailableSuffix = 1..($highestValue + 1) | Where-Object{$existingSuffixes -notcontains $_} | Sort-Object desc | Select -First 1
$nextAvailableSuffix would contain 4 using the above example. We add 1 to highest value in case the only other one is 2 so that way there will only be an answer to $nextAvailableSuffix

Powershell & Get-ADUser - Part 2 - Putting A & B Together

I posted a question some time ago and it was answered to accomplish what I needed - Powershell & Get-ADUser - Split OU getting the 3rd, 4th and 5th elements from the end
ANSWER GIVEN
Get-ADUser -Filter * -Properties CanonicalName |
select -ExpandProperty CanonicalName |
ForEach-Object {
$Parts = $_.split('/')
$Object =
[PSCustomObject]#{
OU = $Parts[1]
Where = $Parts[2]
Type = 'N/A'
}
if ($Parts.count -ge 5)
{ $Object.Type = $Parts[3] }
$Object
}
Now I need to add some other fields (Name, SAM Account, Email) and do a check on the email*. This is my second query.
Powershell Query
$Headers= #{Label="OU Path";Expression={$_.CanonicalName}},
#{Label="Distinguished Name";Expression={$_.DistinguishedName}},
#{Label="Name";Expression={$_.DisplayName}},
#{Label="SAM Account";Expression={$_.SAMAccountName}},
#{Label="E-mail";Expression={$_.EmailAddress}},
Get-ADUser -Filter * -Properties
CanonicalName,DistinguishedName,DisplayName,
SamAccountName,EmailAddress | Select $Headers
How can I combine what it was provided in the answer of my previous question and this query to have the following output?
Also I need to check the email and if #company.com then "EType" = YES.
EXPECTED END RESULT
OU Where Type Name SAM Email *EType
-----------------------------------------------------------------------------
DS AMS N/A Name1 brname1 name1#company.com YES
DS TECHM N/A Name2 xsname2 name2#company.com YES
Developers CH Agencies Name3 agname3 name3#gmail.com NO
Developers CH Market Name4 chname4 name4#company.com YES
Developers HK Agencies Name5 agname5 name5#other.com NO
Developers HK Market Name6 hkname6 name6#company.com YES
Thank you in advance!
I believe you are looking for something like if your #Headers where correct:
Get-ADUser -Filter * -Properties CanonicalName,DistinguishedName,DisplayName,SamAccountName,EmailAddress | Select-Object #{Label="OU Path";Expression={$_.CanonicalName.Split("/")[1]}},
#{Label="Where";Expression={$_.CanonicalName.Split("/")[2]}},
#{Label="Type";Expression={if ($_.CanonicalName.Split("/").Count -ge 5) { $_.CanonicalName.Split("/")[3] } else { 'N/A' }}},
#{Label="Distinguished Name";Expression={$_.DistinguishedName}},
#{Label="Name";Expression={$_.DisplayName}},
#{Label="SAM Account";Expression={$_.SAMAccountName}},
#{Label="E-mail";Expression={$_.EmailAddress}},
#{Label="EType";Expression={ if($_.EmailAddress.IndexOf("#company.com") -gt -1) { 'YES' } else { 'NO' } }} | Format-Table