evotecit / adessentials Goto Github PK
View Code? Open in Web Editor NEWPowerShell Active Directory helper functions to manage healthy Active Directory
License: MIT License
PowerShell Active Directory helper functions to manage healthy Active Directory
License: MIT License
Hi,
Great job. but, could I suggest some omprovements to do
It will be better if all functions have block-comment (synopsis, parameters, examples, ...)
Edit : Ok, you have some external help in .md files (probably generated automatically with PlatyPS module :-) ) but not complete. No example of use.
Better displayed code : avoid Oneliner command (cmdlet1 |cmdlet2 |cmdlet3)
Prefer this
Cmdlet1 |
Cmdlet2 |
Cmdlet3
Easy to realize this with VSCode
Pass PSScriptAnalyzer module on the code to respect Best-Practices.
Put all functions used in module in differents .ps1 files : Easier to read, to debug for you ...
The .psm1 file could easily find them if there are all n a subfoder (Public / Private). No change to do in you psm1 file when you'll add a new function :-)
Edit 2 : It's done on Github but not on the 0.41 version in PS Gallery.
5 Use and abuse Splat More readable code
i.e. : $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC
very long line
Simple suggest, of course
Regards
Olivier
When a group/member has "(" (bracket) in its name, the get-WinADGroupMember* functions will error out, most likely due to how group names are parsed. Bracket symbol is the only character causes problem for me, but I'd assume more special characters need escaping.
Hi, i love the Show-Winadgroupmember Command, it makes it possible to visualize our organisational roles hierarchicaly. If I get the hierarchy of a large scope of groups the problem is the list in the upper part of the browser window. Would it be possible to make it searchable and colapsed? Its hard to find the right group to select if there is a large number.
When I run Show-WinADGroupMember -GroupName 'groupName' -Filepath C:\groupmembership.html all is well for all smaller groups of up to ~60 users and groups. However when I run it against a group with expected ~300+ results with many nested groups I get the error:
Warning: Show-WinADGroupMember - Error processing group groupName. Skipping. Needs investigaition why it failed. Error: Cannot index into a null array
I checked against another large group without any nested groups and although it took a while I did not receive that error and it worked. Is there a limit to results or number of nested groups?
The code that resolves A/AAAA records for domain controllers in Get-WinADForestControllerInformation
is mistakenly including A/AAAA records returned in the "Additional" section of the DNS response. This can lead to an invalid result for the IPAddress*
properties in the output object.
ADEssentials/Public/Get-WinADForestControllerInformation.ps1
Lines 51 to 55 in 8b9b71a
As an example that can be publicly queried, take a look at the response for github.com
. There is only a single A record in the Answer section. But there are a whole bunch of NS records in the Authority section and corresponding A records for those NS records in the Additional section. So when the code in the function filters only on the Type
property, it mistakenly grabs those A records from the additional section as well.
PS> Resolve-DnsName github.com -DnsOnly | Where-Object { $_.Type -eq 'A' }
Name Type TTL Section IPAddress
---- ---- --- ------- ---------
github.com A 2 Answer 140.82.114.4
dns1.p08.nsone.net A 59995 Additional 198.51.44.8
dns2.p08.nsone.net A 59995 Additional 198.51.45.8
dns3.p08.nsone.net A 59995 Additional 198.51.44.72
dns4.p08.nsone.net A 59995 Additional 198.51.45.72
ns-1283.awsdns-32.org A 88165 Additional 205.251.197.3
ns-1707.awsdns-21.co.uk A 88166 Additional 205.251.198.171
ns-520.awsdns-01.net A 88166 Additional 205.251.194.8
The code should ideally be updated to only include results in the Answer section.
An option to specify credentials when using another forest
Get-WinADDFSHealth -Forest another.forest.com -Credential 'forest\iamauser'
Hello, I am trying to clean up my AD. How can I use ADEssentials to find nested duplicate users inside groups?
Thanks
you dont even have a command in these functions to get the data.
get-winadproxyaddresses
Get-WinADUserPrincipalName
perhaps others as well im not going to go through all of them
Using Testimo, under the Orphaned Administrative Objects (AdminCount) section, Get-WinADPrivilegedObjects
is identifying a group and one of its member groups as orphaned, but the first group is a member of the Built-In Administrators group and correctly has an AdminCount of 1.
For the function Get-WinADTombstoneLifetime, when the property tombstoneLifetime does not exist the existing function code does not catch it in the if statement and therefore the result is $null in the returned value from the function. The code should be altered slightly to look for $output.tombstoneLifetime, not just $output
existing code:
function Get-WinADTomebstoneLifetime { [Alias('Get-WinADForestTomebstoneLifetime')] [CmdletBinding()] param( [alias('ForestName')][string] $Forest, [System.Collections.IDictionary] $ExtendedForestInformation ) $ForestInformation = Get-WinADForestDetails -Forest $Forest -ExtendedForestInformation $ExtendedForestInformation # Check tombstone lifetime (if blank value is 60) # Recommended value 720 # Minimum value 180 $QueryServer = $ForestInformation.QueryServers[$($ForestInformation.Forest.Name)]['HostName'][0] $RootDSE = Get-ADRootDSE -Server $QueryServer $Output = (Get-ADObject -Server $QueryServer -Identity "CN=Directory Service,CN=Windows NT,CN=Services,$(($RootDSE).configurationNamingContext)" -Properties tombstoneLifetime) if ($null -eq $Output) { [PSCustomObject] @{ TombstoneLifeTime = 60 } } else { [PSCustomObject] @{ TombstoneLifeTime = $Output.tombstoneLifetime } } }
proposed code change:
if ($null -eq $Output.tombstoneLifetime) { [PSCustomObject] @{ TombstoneLifeTime = 60 } } else { [PSCustomObject] @{ TombstoneLifeTime = $Output.tombstoneLifetime }
When running Test-DNSNameServers receive the comment, "Failed to get the zone information for contoso.com on server DC1.contoso.com...."
This appears to be a result of the Jan 2024 commit Convert scriptblock filter to string filter
PS > Get-ADDomainController -Server $domain -Filter "IsReadOnly -eq '$False'"|measure
Count : 0
Average :
Sum :
Maximum :
Minimum :
Property :
PS > Get-ADDomainController -Server $domain -Filter {IsReadOnly -eq $false}|measure
Count : 17
Average :
Sum :
Maximum :
Minimum :
Property :
I'm not sure how to write this as a string filter that will work. I know the AD cmdlets often cause me to revert to scriptblock filters.
Tested on Windows 10 22H2, with PowerShell 5.1.19041.4291
Do you have an AADEssentials version? I like the way this works with On-premises netsed groups and memberships, but wondering if you have an equivalent for AAD (Azure AD)
Dear
The module does not support objects of type "msDS-GroupManagedServiceAccount", which throws warnings in GPOZaurr "WARNING: Get-WinADObject - Unsupported object (S-1-5-21-xxxxxx-2233) of type msDS-GroupManagedServiceAccount. Only user,computer,group and foreignSecurityPrincipal is supported."
This type of account may be used as AGPM service account.
Please consider to enhance module to support group managed service accounts.
Thank you
Best regards
Patrick
From email
I used the Show/Get-WinADGroupMemberOf command to figure user groups membership in a large domain, but I noticed a small discordance in the results it is providing and I found what it is.
Here's the situation. In the domain, there are some groups with the same group name, BUT the SamAccountName is unique (legacy stuff from domain merge a few years ago).
In the Get-WinADGroupMemberOf script, you're using the $NestedMember.name as the ParentGroup, so the reporting is not accurate.
For example :
Group 1
o Name : SecRole-DBAdmins
o SamAccountName : SecRole-DBAdmins
Group 2 :
o Name : SecRole-DBAdmins
o SamAccountName : SR-DBAdmins
So in the case where a user is a member of both groups, the child groups of "SR-DBAdmins" show "SecRole-DBAdmins" as the ParentGroup, which is not exactly the case.
And it creates some discordances in the graphics generated with Show-WinADGroupeMemberOf...
Hoping you can resolve this issue shortly, let me know when you release a new version of the scripts and I'll give it a try to see if my issue is resolved
when running something like Show-WinADGroupMemberOf it works terrifically, but my request is, if the Group Name being returned is an AAD Group can it be written in to translate the 365 Group Writeback name to the DisplayName as it iterates through?
(365 Group Writeback doesn't put human readable value in the Name attribute, but it does put it in the DisplayName attribute.)
I've written a proof of concept that allows me to do it by hand (even if inefficient)
Reference for GUID translation here https://tech.nicolonsky.ch/validating-a-guid-with-powershell/
The thinking for my example was:
I'm sure you'll have a more efficient method, but this is my submission to improve your awesome script.
$GUIDRegex = '(?im)^[{(]?[0-9A-F]{8}[-]?(?:[0-9A-F]{4}[-]?){3}[0-9A-F]{12}[)}]?$'
$userGroups = Get-AdPrincipalGroupMembership -Identity '<username>'
ForEach ($group in $userGroups) {
$AdGroup = Get-ADGroup -Identity $group -Properties DisplayName
If (
$AdGroup.Name -match 'Group_' -and
$AdGroup.Name.trimStart('Group_') -match $GUIDRegex
) {
Write-Output -InputObject "AAD - $($AdGroup.DisplayName)"
} Else {
Write-Output -InputObject $AdGroup.Name
}
}
From the resource I referenced he wrote a Function to assist:
Function Test-Guid {
<#
.SYNOPSIS
Validates a given input string and checks string is a valid GUID
.DESCRIPTION
Validates a given input string and checks string is a valid GUID by using the .NET method Guid.TryParse
.EXAMPLE
Test-Guid -InputObject "3363e9e1-00d8-45a1-9c0c-b93ee03f8c13"
.NOTES
Uses .NET method [guid]::TryParse()
#>
[Cmdletbinding()]
[OutputType([bool])]
Param (
[Parameter(Mandatory,Position=0,ValueFromPipelineByPropertyName=$true)]
[AllowEmptyString()]
[string]$InputObject
)
Process{
return [guid]::TryParse($InputObject, $([ref][guid]::Empty))
}
}
Test-LDAP can't find any ports or the FQDN.
I tracked this down to these lines in function Test-LDAP in ADEssentials.psm1:line 5858
foreach ($Computer in $ComputerName) {
[Array] $ADServerFQDN = (Resolve-DnsName -Name $Computer -ErrorAction SilentlyContinue)
if ($ADServerFQDN) {
if ($ADServerFQDN.NameHost) { $ServerName = $ADServerFQDN[0].NameHost } else {
[Array] $ADServerFQDN = (Resolve-DnsName -Name $Computer -ErrorAction SilentlyContinue)
$FilterName = $ADServerFQDN | Where-Object { $_.QueryType -eq 'A' }
$ServerName = $FilterName[0].Name
}
} else { $ServerName = '' }
In the line:
if ($ADServerFQDN.NameHost) { $ServerName = $ADServerFQDN[0].NameHost } else {
When I tested I found:
$ADServerFQDN.NameHost
is true but $ADServerFQDN[0].hostname
is null and is the AAAA record
$ADServerFQDN[1].hostname
is null and the A record
$ADServerFQDN[2].hostname
is NOT null and is the first NS record and has a namehost value but not the FQDN of $computer, it is the FQDN of the first nameserver.
namehost only exists in NS records, not A or AAAA records
For that statement to work, you need to reference $ADServerFQDN[0].Name
not .namehost.
if ($ADServerFQDN.NameHost) could probably reference .name or .namehost. I am not sure if it makes a difference.
The change that worked for me is:
if ($ADServerFQDN.Name) { $ServerName = $ADServerFQDN[0].Name } else {
However, I am not sure why that first check is there, the code after the else which finds the A record is probably the better way to find the FQDN of $computer but what you really need to look for are the answer records.
This might be a better way to find the FQDN of $computer, it will find the answer records to the DNS query and extract the FQDN from them.
foreach ($Computer in $ComputerName) {
$ServerName = ""
[Array] $ADServerFQDN = (Resolve-DnsName -Name $Computer -ErrorAction SilentlyContinue)
$FilterName = $ADServerFQDN | Where-Object { $_.section -eq 'answer' }
$ServerName = $FilterName[0].Name
Ronald Schubot
Western Michigan University
If you create 4 groups with membership like this:
"A","B","C","D" | foreach {New-ADGroup $_ -GroupScope Universal}
Add-ADGroupMember A -Members "B"
Add-ADGroupMember B -Members "C","D"
Add-ADGroupMember C -Members "D"
The following command:
Get-WinADGroupMemberOf D -ClearCache | select ObjectName, ParentGroup, Name, CircularIndirect
reports the following:
ObjectName ParentGroup Name CircularIndirect
---------- ----------- ---- ----------------
D D C False
D C B False
D B A False
D D B True
D B A False
In some circumstances, this is a poor AD group design, but it is not "circular", right? Or am I misunderstanding the intent of this field?
Hi,
I love this tool and have been using this for months now. It has been a while since I checked all the changes, so please forgive me if this issue was already identified and remediated. I have hundreds of domain controllers. Is there any way to limit the scope of DCs to only a hand full to get the general forest/domain health checks? I would like to exclude the majority of the domain controllers like the type of checks I can run. Is there already something available to limit the scope of domain controllers that I missed somewhere, or, is this a new feature that would need to be added?
Thanks in advance,
M
I am trying to install ADEssentials, and I keep getting hung up with either the PSEventViewer module or the PSWriteHTML module. It keeps saying that the module is not atching with the authenticode issuer. I've installed the modules individually, but the install script still fails out when it gets to that installation piece. I've uninstalled the modules and let the ADEssentials install script try to do its thing but to no avail. Please let me know if there are any workarounds for this. Here is a quick snippet of the error I see.
`VERBOSE: Hash for package 'ADEssentials' does not match hash provided from the server.
VERBOSE: InstallPackageLocal' - name='ADEssentials', version='0.0.145',destination='C:\Users\rodolfo.ybarra\AppData\Local\Temp\689443725'
VERBOSE: Catalog file 'PSEventViewer.cat' is not found in the contents of the module 'PSEventViewer' being installed.
VERBOSE: Valid authenticode signature found in the file 'PSEventViewer.psd1' for the module 'PSEventViewer'.
VERBOSE: For publisher validation, current module 'PSEventViewer' with version '1.0.22' with publisher name 'System.Object[]'. Is this module signed by Microsoft: 'False'.
VERBOSE: For publisher validation, using the previously-installed module 'PSEventViewer' with version '1.0.22' under 'C:\Program Files\WindowsPowerShell\Modules\PSEventViewer\1.0.22' with publisher name 'System.Objec
t[]'. Is this module signed by Microsoft: 'False'.
PackageManagement\Install-Package : Authenticode issuer 'System.Object[]' of the new module 'PSEventViewer' with version '1.0.22' is not matching with the authenticode issuer 'System.Object[]' of the
previously-installed module 'PSEventViewer' with version '1.0.22'. If you still want to install or update, use -SkipPublisherCheck parameter.
At C:\Program Files (x86)\WindowsPowerShell\Modules\PowerShellGet\1.0.0.1\PSModule.psm1:1809 char:21
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
PS C:\Windows\system32> `
This line in Get-WinADGroupMemberOf passes the Identity parameter to Get-WinADObject
And with an @ symbol this breaks here in Get-WinADObject
ADEssentials/Public/Get-WinADObject.ps1
Line 164 in d316cc9
I've looked at the source, I'm not smart enough to add the mail address to group output if present.
Is this something that can be added? We have universal groups being used as DL's, I've got the need to know about this within our group reporting.
Any thoughts?
Thanks,
Joe
Is there a way to display only certain levels of descendents of an AD group using Show-WinADGroupMember
e.g. immediate descendents or first 2 levels of descendents etc?
We have a use case where we do not need to view all the nested groups but only the first 1 or 2 levels of nested groups.
Thanks.
Hi
thanks for the amazing module!
I just spent some time generating reports and was wondering if there is a way to merge some of them together?
Basically i missed a group and created reports for Groupo A and B but later noticed that they both are Members of Group C.
Would it be possible to merge the existing Reports of A and B instead of running the command again for Group C?
Hi,
I downloaded and imported this module. When I try to run the Get-WinADDiagnostics cmdlet, it calls Get-WinADForestDetails unsuccessfully. I looked in both the Public and Private folders and didn't see this command referenced anywhere. It's also not native to powershell or RSAT tools. Any idea what happened to it?
Hi,
Some reason not all functions are loading completely. I dont see good amount of functions. when i do import-module ADEssentials, it loads ok, no errors etc. but when i do Get-Command -Module ADEssentials I only see limit number of functions loaded. AM i missing something?
PS C:\Program Files\WindowsPowerShell\Modules\ADEssentials\0.0.148> Get-Command -Module ADESsentials
CommandType Name Version Source
Alias Get-WinADTrusts 0.0 ADEssentials
Alias Get-WinDNSServerIP 0.0 ADEssentials
Alias Show-ADGroupMember 0.0 ADEssentials
Alias Show-ADGroupMemberOf 0.0 ADEssentials
Alias Show-ADTrust 0.0 ADEssentials
Alias Show-ADTrusts 0.0 ADEssentials
Alias Show-WinADTrusts 0.0 ADEssentials
Function Get-DNSServerIP 0.0 ADEssentials
Function Get-WinADDomain 0.0 ADEssentials
Function Get-WinADForest 0.0 ADEssentials
Function Get-WinADGroupMember 0.0 ADEssentials
Function Get-WinADGroupMemberOf 0.0 ADEssentials
Function Get-WinADObject 0.0 ADEssentials
Function Get-WinADTrust 0.0 ADEssentials
Function Get-WinDNSIPAddresses 0.0 ADEssentials
Function New-ADSite 0.0 ADEssentials
Function Set-WinADForestACLOwner 0.0 ADEssentials
Function Show-WinADDNSRecords 0.0 ADEssentials
Function Show-WinADGroupMember 0.0 ADEssentials
Function Show-WinADGroupMemberOf 0.0 ADEssentials
Function Show-WinADTrust 0.0 ADEssentials
Function Test-WinADVulnerableSchemaClass 0.0 ADEssentials
thank you!
Can this be used on Azure AD? If so how do I connect?
does this work in your environment this way?
I've stumbled accross the problem, that the "RIDmaster" test always failed, except on the DC holding that role.
while running the test locally on a non-DC server it works:
dcdiag /v /test:RidManager /s:$DomainController
I've found this comment where a kerberos double-hop problem was mentioned as the issue's cause.
https://stackoverflow.com/questions/56061971/dcdiag-returns-different-output-in-powershell
do you have any idea or experience with that?
PS C:\Windows\system32> Show-ADGroupMember -Identity support -online
WARNING: Add-HTMLStyle - File C:\Program Files\WindowsPowerShell\Modules\PSWriteHTML\1.0.0\..\Resources\CSS\datatables.noscript.css not found.
Unable to load CSS to HTML. HTML may be broken. Skipping.
WARNING: Add-HTMLScript - File C:\Program Files\WindowsPowerShell\Modules\PSWriteHTML\1.0.0\..\Resources\JS\dataTables.conditions.js not found
. Unable to load JavaScript to HTML. HTML may be broken. Skipping.
WARNING: Add-HTMLScript - File C:\Program Files\WindowsPowerShell\Modules\PSWriteHTML\1.0.0\..\Resources\JS\escapeRegex.js not found. Unable t
o load JavaScript to HTML. HTML may be broken. Skipping.
WARNING: Add-HTMLScript - File C:\Program Files\WindowsPowerShell\Modules\PSWriteHTML\1.0.0\..\Resources\JS\tabbis.js not found. Unable to loa
d JavaScript to HTML. HTML may be broken. Skipping.
WARNING: Add-HTMLScript - File C:\Program Files\WindowsPowerShell\Modules\PSWriteHTML\1.0.0\..\Resources\JS\tabbisAdditional.js not found. Una
ble to load JavaScript to HTML. HTML may be broken. Skipping.
Looks like the issue is here: C:\Program Files\WindowsPowerShell\Modules\PSWriteHTML\1.0.0\..\
The files that can't be found in the first error are here: C:\Program Files\WindowsPowerShell\Modules\PSWriteHTML\1.0.0\Resources\CSS
There are far more errors if you don't use the -online flag.
If there are WAN-Optimizer in an active directory environment this function is not working.
This is because Get-ADDomainController -Filter *
Small change and it will also work in these environments.
#$DomainControllers = Get-ADDomainController -Filter $Filter -Server $QueryServer -ErrorAction Stop
$DomainControllers = (Get-ADDomain -Identity $Domain).ReplicaDirectoryServers | ForEach { Get-ADDomainController -Identity $_ -Server $QueryServer }
This change should be done in multiple modules our maybe a chance for refactoring?
PackageManagement\Install-Package : Authenticode issuer 'CN=Przemysław Kłys EVOTEC, O=Przemysław Kłys
EVOTEC, L=Mikołów, C=PL' of the new module 'PSWriteHTML' with version '1.9.0' from root certificate
authority 'CN=DigiCert Trusted Root G4, OU=www.digicert.com, O=DigiCert Inc, C=US' is not matching with
the authenticode issuer 'CN=Przemysław Kłys EVOTEC, O=Przemysław Kłys EVOTEC, L=Katowice, S=Śląskie, C=PL'
of the previously-installed module 'PSWriteHTML' with version '0.0.190' from root certificate authority
'CN=DigiCert Assured ID Root CA, OU=www.digicert.com, O=DigiCert Inc, C=US'.
A parameter for Show-ADGroupMember that would allow the user to generate the HTML report without invoking it would be very useful for automating regular report generation. I'm keen to schedule regular reports of my top level group memberships and invoking 1275 reports as they're being generated isn't ideal.
Hi,
ADEssentials is very great but I think it could be useful to verify if PowerShell is launched as admin (prevent running or maybe only alert user if it not).
I guess you already know how to to that but if you need.
Because, for some tests, admin context is needed or we get an unexpected and unreal value (for example with Get-WinADDFSHealth).
Of course, same think is true with Testimo.
Hello
First of all, great job on the ADEssentials development. Very helpful.
when using the cmdlet Show-WinADGroupMemberOf and Show-WinADGroupMember, it would be nice to have a way to have custom the nodes logos, based on the names of the node and keywords found.
We have applied a very strict naming convention to our groups and users, therefore they are easy to tag with specific icons.
For examples :
this way, the graphic mapping will be much easier to read and identification of ressource groups will be very easy to do to determinate what are the access of the user.
a mapping file could be provided with a list of strings (allowing wildcard) and the associated link to the flaticon logo.
thanks
Hello there,
When I ran the command "Show-WinADGroupMember" I put the FilePath parameter value to "C:\Temp\Reports\GroupMembership.html", but it errored out with the following.
Unless I am missing something, it doesn't automatically create the folder if it doesn't exist. I'm not sure if there is another parameter like "-Force" or something that I can use, or if this is something that needs added to the code.
Thanks.
Ryan
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.