I'm trying to create one e-mail per manager, where the user accounts he is responsible for in active directory, will expire within a given time frame. It's already working quite well but I have some issue in grouping the Managers.
It would be nice if it was possible to group the managers together and then collect all the users where that specific manager is responsible for in a small HTML table ($Rows). The HTML-code is not the problem, but iterating the users for that manager is my issue.
The code:
$OU = 'OU=BBB,OU=EU,DC=domain,DC=net', 'OU=AAA,OU=EU,DC=domain,DC=net'
[INT]$Days = 30
$ExpUsers=$Objects=#()
Foreach ($O in $OU) {
$ExpUsers += Search-ADAccount -AccountExpiring -TimeSpan "$Days.00:00:00" -UsersOnly -SearchBase $O |
Select -ExpandProperty SamAccountName
}
Foreach ($E in $ExpUsers) {
$User = Get-ADUser $E -Properties * | Select SamAccountName, EmailAddress, GivenName,
SurName, AccountExpirationDate, Manager, DisplayName
$Manager = Get-ADUser $User.Manager -Properties * | Select SamAccountName, EmailAddress, GivenName, SurName
$Objects += [PSCustomObject]#{
User = $User
Manager = $Manager
}
}
$Objects | Group-Object Manager.SamAccountName | % {
$Rows=#()
foreach($M in $_) {
# Create HTML row for each user with the same manager
$M.Group.Manager.SurName
}
}
Grouping the managers can be done easily like this:
$Objects.Manager | Group-Object SamAccountName
However, when I do it like this I can't use the User properties anymore because they haven't been piped to Group-Object.
What is the best way to overcome this hurdle? I could of course create my object like this:
$Objects += [PSCustomObject]#{
UserSamAccountName = $User.SamAccountName
UserGivenName = $User.GivenName
ManagerSamAccountName = $Manager.SamAccountName
ManagerGivenName = $Manager.GivenName
}
But this solution doesn't seem to be so flexible if I want to add stuff later on.
I've found my answer in an example here:
$Objects | Group-Object {$_.Manager.SamAccountName} | % { $_.Group.User.GivenName}
Related
I'm trying to get a list of emails from an ADGroup. The problem is there are members in the group that have no emails and I want the variable to show the ADuser instead of the email if the field is empty.
current code is this
$emails = get-adgroupmember gg-sccm-admins | get-aduser -Properties emailaddress | select emailaddress
$emails+= get-adgroupmember gg-sccm-site_admins | get-aduser -Properties emailaddress | select emailaddress
Write-Output $emails
My idea was to use an IF and if the email field is empty write username in the variable but I can't get it to work.
I guess you could use below:
$emails = get-adgroupmember gg-sccm-admins | Get-ADUser -Properties emailaddress | select #{N="EmailAddress";E={if($_.emailaddress){$_.emailaddress}else{$_.samaccountname}}}
$emails+= get-adgroupmember gg-sccm-site_admins | Get-ADUser -Properties emailaddress | select #{N="EmailAddress";E={if($_.emailaddress){$_.emailaddress}else{$_.samaccountname}}}
Write-Output $emails
Why not just do both?
[System.Collections.ArrayList]$Emails = #()
[System.Collections.ArrayList]$Names = #()
[array]$GroupMembers = Get-ADGroupMember "gg-sccm-admins" | Get-ADUser -Properties emailaddress,DisplayName | Select EmailAddress, DisplayName
Foreach($user in $GroupMembers){
$null = $Emails.Add($User.EmailAddress)
$null = $Names.Add($User.DisplayName)
}
For($i=0;$i -lt $Names.count;$i++){
[pscustomobject]#{
"User Names" = $Names[$i]
"Emails" = $Emails[$i]
}
}
Also, instead of using a fixed array, I changed it to an ArrayList. Should make the process faster as it doesn't have to recreate each array each time with a new item.
I need to get the following into a CSV:
Groups with fields: group name, group SID, group email address, group type
and then for each of the above groups i need the member users with the fields: group name (I know that's a repeat), userID, user firstname, userlastname, user email.
If someone has a solution for this I will be forever grateful. The solution will be something I can study and learn from so thanks again.
I have the two pieces separately but am stuck at that point
Get-ADGroup -Filter * -Properties Member |
select Name, DistinguishedName, sid, GroupCategory, GroupScope,
#{Name="Members";Expression={($_.Members | Measure-Object).Count}} |
Out-GridView
#Export-Csv c:\rmm-mgmt\test.csv
I don't understand why the user details can't just be added as well.
For the users I'm using:
Get-ADUser -Filter * -Properties * |
Select-Object name, surname, givenname, displayname, emailaddress |
Out-GridView
(Using Out-GridView to check results before I begin exporting)
As you can see these are two pieced of information I can get but can't put them together. One example is I can't get the list of members in groups.
You have to use ForEach-Object and assign the group to a named variable so you can access it where you format each member. Then pipe $_.Member into Get-ADUser:
Get-ADGroup -Filter * -Properties Member | ForEach-Object {
$group = $_
$_.Member | Get-ADUser -Properties Surname,GivenName,DisplayName,EmailAddress |
Select #{N = "Group Name";E = {$group.Name}}, Surname, GivenName, DisplayName, EmailAddress
} | Out-GridView
I'm not quite clear on the exact output formatting you are looking for but I believe something like this should work for what you are attempting to do.
# Create an empty array we will add our custom objects to.
$outputObjs = #()
# Get all the AD groups.
$groups = Get-ADGroup -Filter * -Properties Member |
select Name, DistinguishedName, sid, GroupCategory, GroupScope |
select -First 40
# Iterate the groups capturing the members of each group.
foreach ($group in $groups) {
$groupMembers = Get-ADGroupMember -Identity $group.Name |
? { $_.objectClass -eq "user" } |
% { Get-ADUser $_.SamAccountName } |
select name, surname, givenname, displayname, emailaddress
# Get the group member count
$memCount = 0
if ($groupMembers) { $memCount = $groupMembers.Count; }
else { $memCount = 0 }
# Iterate through the group members creating a custom object and adding it to our object array.
foreach($member in $groupMembers) {
$outputObjs += New-Object -TypeName psobject -Property (#{
'Group Name' = $group.Name;
'Group DN' = $group.DistinguishedName;
'Group SID' = $group.SID;
'Group Category' = $group.GroupCategory;
'Group Scope' = $group.GroupScope;
'Group Member Count' = $memCount
'User Name' = $member.name;
'User Surname' = $member.surname;
'User Given Name' = $member.givenname;
'User Display Name' = $member.displayname;
'User Email Address' = $member.emailaddress
})
}
}
# Convert the object array to a CSV.
$outputObjs | ConvertTo-Csv -NoTypeInformation
Your CSV would look like this
"Group Name","User Surname","User Given Name","Group Member Count","User Email Address","Group SID","User Name","Group Category","Group DN","User Display Name","Group Scope"
I have the following code:
$FilePath_Prefix = "C:\temp\UserLastLogon-"
function Msg ($Txt="") {
Write-Host "$([DateTime]::Now) $Txt"
}
#Cycle each DC and gather user account lastlogon attributes
$List = #() #Define Array
(Get-ADDomain).ReplicaDirectoryServers | Sort | % {
$DC = $_
Msg "Reading $DC"
$List += Get-ADUser -Server $_ -Filter "samaccountname -like '*'" -Properties LastLogon |
Select samaccountname, lastlogon, #{n='DC';e={$DC}}
}
Msg "Sorting for most recent lastlogon"
$LatestLogOn = #() #Define Array
$List | Group-Object -Property samaccountname | % {
$LatestLogOn += ($_.Group | Sort -prop lastlogon -Descending)[0]
}
$List.Clear()
$FileName = "$FilePath_Prefix$([DateTime]::Now.ToString("yyyyMMdd-HHmmss")).csv"
try {
$LatestLogOn |
Select samaccountname, lastlogon,
#{n='lastlogondatetime';e={[datetime]::FromFileTime($_.lastlogon)}}, DC |
Export-CSV -Path $FileName -NoTypeInformation -Force
Msg "Exported results. $FileName"
} catch {
Msg "Export Failed. $FileName"
}
I use it to interrogate AD Users for most up-to-date lastLogon information across all my domains. It works, and it works really fast.
Now, I need to get more details into my output, such as givenName and Surname lets say.
What would be the best approach to achieve this, because I don't want to interrogate redundantly all my DC's for those kind of attributes.
My idea here, is to create another array with Get-ADuser -Filter * -Properties givenName, surname, etc..etc and then bind together the two arrays. And I don't seem to get it right. Could someone help, or point me in the right direction to achieve this task.
I would fetch all the last logon information and save it into a hash table, which is designed for fast lookups. I'd try something like this:
$FileName = 'C:\temp\UserLastLogon-{0:yyyyMMdd-HHmmss}.csv' -f [DateTime]::Now
$DCs = (Get-ADDomain).ReplicaDirectoryServers | Sort-Object
# This *may* be more semantically accurate; I don't remember how it works with multiple domains
# $DC = Get-ADDomainController -Filter * | Select-Object -ExpandProperty HostName | Sort-Object
# Create the hash table
$UserLogonInfo = #{}
foreach ($DC in $DCs) {
Write-Host "Reading logon data from $DC..."
# Fetch all users that have a LastLogon value from the current DC
# We specify LastLogon>=1 because some users that never log on have LastLogon of
# 0 or null, both of which would show up as Jan 1, 1601.
Get-ADUser -Server $DC -LDAPFilter '(LastLogon>=1)' -Properties LastLogon | ForEach-Object {
if (!$UserLogonInfo.ContainsKey($_.DistinguishedName)) {
# If the accountname doesn't exist, add it
$UserLogonInfo[$_.DistinguishedName] = #{
LastLogon = $_.LastLogon
LastLogonDateTime = ([DateTime]::FromFileTime($_.LastLogon))
LastLogonDC = $DC
}
}
elseif (($UserLogonInfo[$_.DistinguishedName].LastLogon -lt $_.LastLogon)) {
# If the account name exists, update it if it's more recent
$UserLogonInfo[$_.DistinguishedName] = #{
LastLogon = $_.LastLogon
LastLogonDateTime = ([DateTime]::FromFileTime($_.LastLogon))
LastLogonDC = $DC
}
}
}
}
Write-Host "Fetching user data..."
Get-ADUser -Filter * -Properties LastLogon, givenName, surname |
Select-Object -Property SamAccountName, givenName, surname,
#{n='LastLogonDateTime';e={$UserLogonInfo[$_.DistinguishedName].LastLogonDateTime}},
#{n='LastLogonDC';e={$UserLogonInfo[$_.DistinguishedName].LastLogonDC}} |
Export-CSV -Path $FileName -NoTypeInformation -Force
If an account has a blank LastLogonDateTime and blank LastLogonDC, then that account has never logged on.
It's more correct to use DistinguishedName instead of SamAccountName as the key for the $UserLogonInfo hash table, and that is essentially a required change if you are querying multiple domains at once. Note that I do mean multiple domains and not merely multiple domain controllers in the same domain (which is what I believe you're actually doing in spite of the question title).
This whole process on my domain with 3 DCs and ~10,000 users takes about 15 seconds.
Note that there are a ton of ways that LastLogon can be inaccurate. It can be updated without a full logon or without an interactive logon and some logons won't force an update of the field. If you really want to track logons, you should use security auditing for logon events.
Edit:
When we populate $UserLogonInfo we're fetching all accounts except for accounts that either don't have a LogonDate attribute at all or when that attribute is 0 or null. Each of those indicate that there has been no login. So, we know that any user that isn't in the $UserLogonInfo hash table has never logged in.
If you want to use some special value for when a user account has never logged on, you should just use an if statement and check to see if the user is in the $UserLogonInfo hash table:
Get-ADUser -Filter * -Properties LastLogon, givenName, surname |
Select-Object -Property SamAccountName, givenName, surname,
#{n = 'LastLogonDateTime'; e = {if ($UserLogonInfo.ContainsKey($_.DistinguishedName)) { $UserLogonInfo[$_.DistinguishedName].LastLogonDateTime } else { 'Never' }}},
#{n = 'LastLogonDC'; e = {if ($UserLogonInfo.ContainsKey($_.DistinguishedName)) { $UserLogonInfo[$_.DistinguishedName].LastLogonDC } else { 'N/A' }}} |
Export-CSV -Path $FileName -NoTypeInformation -Force
The thing is that you're actually asking your DCs for that data as it's returned by default. So the only thing to add is
, givenName, surname
to your Select-Object. Check this by running
Get-AdUser yourLogin -Properties LastLogon
You'll receive the following properties in your output: DistinguishedName Enabled GivenName LastLogon
Name
ObjectClass
ObjectGUID
SamAccountName
SID
Surname
UserPrincipalName
So the only thing you lose will be some memory. The alternative approach would be to create another array using
$names = Get-ADuser -Filter * | Select-Object SamAccountName, givenName, surname
and then match the date based on SamAccountName like this:
$LatestLogOn = #() #Define Array
$List | Group-Object -Property samaccountname | % {
$t = ($_.Group | Sort -prop lastlogon -Descending)[0]
$n = $names | Where-Object samaccountname -eq $t.samaccountname
$LatestLogOn += $t | select *, #{n="givenName";e={$n.givenName}}, #{n="surname";e={$n.surname}}
}
I am still learning my way around PowerShell and I'm trying to get some data out of Intermedia using their Intermedia HostPilot PowerShell tool.
First I start out by adding all the Distribution Group information to my $Groups array:
$Groups = Get-DistributionGroup
I am able to get the DisplayName and EmailAddress of those in Distribution Groups, however I can't tell which user is in which group:
for ($i=0; $i -lt $Groups.length; $i++)
{ Get-DistributionGroupMember -Identity $Groups[$i].DistinguishedName |
Select DisplayName, EmailAddress }
I found the script below online (https://www.morgantechspace.com/2015/06/powershell-export-distribution-list-members-to-csv.html) which was helpful but I still don't see the members of the group in my csv file, just a list of the distribution groups:
$Groups = Get-DistributionGroup
$Groups | ForEach-Object {
$group = $_.GUID
$members = ''
Get-DistributionGroupMember $group | ForEach-Object {
If($members) {
$members=$members + ";" + $_.GUID
} Else {
$members=$_.GUID
}
}
New-Object -TypeName PSObject -Property #{
GroupName = $group
Members = $members
}
} | Export-CSV "C:\\Distribution-Group-Members.csv" -NoTypeInformation -Encoding UTF8
Ideally I would like to have an additional column that displays the Distribution Group for each user. Something like this:
DistributionGroup DisplayName EmailAddress
accounting Rob Smith rob.smith#yahoo.com
accounting John Quincy john.quincy#yahoo.com
This is one variation I tried:
for ($i=0; $i -lt $Groups.length; $i++)
{ Get-DistributionGroupMember -Identity $Groups[$i].DistinguishedName |
Select DisplayName, EmailAddress, $Groups[$i].DisplayName }
This just gives me a heading with the name of the first distribution group, like this:
DisplayName EmailAddress Accounting
Any tips are welcome. Thanks!
I really don't know what the Intemedia HostPilot powershell commands are but in plain powershell you could go with something like that :
$DGroups = Get-ADGroup -Filter {(GroupCategory -eq "Distribution")} | select Name
Foreach ($Group in $DGroups)
{
write-host""
write-host "Members of the >>> "$Group.Name" <<< are:"
write-host "--------"
$Users = Get-ADGroupMember $Group.Name | Select samaccountname | sort Samaccountname
Foreach ($user in $users)
{
Get-ADUser -Identity $user.samaccountname -Properties * | select Name, Samaccountname, mail, displayname
}
}
I am posting it as an answer as the code in the comments is not displayed really well.
The $Result object matches the desired format example you gave and is ready to be output to the console, piped to Out-GridView, or exported to CSV.
$DGroups = Get-DistributionGroup
$Result = #()
foreach ( $DGroup in $DGroups ) {
$DGroup | Get-DistributionGroupMember | Select-Object DisplayName, EmailAddress | ForEach-Object {
$Result += [PSCustomObject]#{
DistributionGroup = $DGroup.DisplayName
DisplayName = $_.DisplayName
EmailAddress = $_.EmailAddress
}
}
}
Hope this helps.
I'm trying to make a script returning AD users ID, Display Name and Group memberships based on an input file and export the result to another file.
However, the group membership information seems to get lost in the process.
Any ideas?
My current script:
$Result = #()
ForEach ($_ in gc userlist.csv)
{
$User = Get-ADUser $_ -Properties DisplayName, SamAccountName, LastLogonDate | Select DisplayName, SamAccountName, LastLogonDate
$Groups = Get-ADPrincipalGroupMembership $_ | Select Name
# So far it seems to work
$Properties = #{
UserID = (#($User.SamAccountName) -join ',')
Name = (#($User.DisplayName) -join ',')
LastLogonDate = (#($User.LastLogonDate) -join ',')
Groups = (#($Groups.Name) -join ',')
}
# By this time, Groups doesn't return any information
$Result += New-Object psobject -Property $Properties
}
$Result | Select-Object Name, UserID, Groups, LastLogonDate | Export-Csv -NoTypeInformation -Path output.csv
Here's what I think might work:
$Result = #()
ForEach ($User in gc userlist.csv) {
$UserDetails = Get-ADUser $User -Properties DisplayName, SamAccountName, LastLogonDate | Select DisplayName, SamAccountName, LastLogonDate
$Groups = Get-ADPrincipalGroupMembership $User | Select Name
$Properties = #{
UserID = $UserDetails.SamAccountName
Name = $UserDetails.DisplayName
LastLogonDate = $UserDetails.LastLogonDate
Groups = ($Groups.Name -join '; ')
}
$Result += New-Object psobject -Property $Properties
}
$Result | Select-Object Name, UserID, Groups, LastLogonDate | Export-Csv -NoTypeInformation -Path output.csv
Per my comment, although using $_ will probably work its not particularly good practice to manually set that variable so i've changed it to $user.
I've therefore changed your $user to $userdetails.
I removed your casting as an array #() for each of the properties as well as your joining them with ,. I'm not sure why this is necessary, except perhaps with the Groups property, but in order to then not confuse the CSV result, i've joined these with a ; instead.