The largest release since 2.0.0: 5 new checks, 16 additions, 138 fixes.
Headlines only below - the full write-up for every entry, with the reasoning
behind it, is in CHANGELOG.md.
Added
- Who can write to the SCCM System Management container is now reported.
- A SCOM service account with privileged rights in the domain is called out.
- The validation now checks that a finding definition can actually appear in a report.
- LAPS passwords that stopped rotating are reported.
- AD FS is found and named.
- A computer whose owner is not its creator is called out separately.
- Disabled computer accounts are included in the owner check.
- Constrained delegation now says whether the target is a Domain Controller.
- Accounts carrying protocol transition without any delegation target are reported.
Get-DomainComputer -DomainController.- A roastable account that holds privileged rights is called out separately.
- ESC10 is detected where it is deployed by Group Policy.
Get-WeakCertificateMapping- new AD CS check for ESC14, explicit certificate mappings.Get-GPOPointAndPrint- new GPO check for Point and Print printer driver policies.- The JSON export survives a scan that does not finish.
-ExcludeModuleruns everything but the named modules.
Changed
- Security descriptors are read in one query instead of one per object.
- An output path is no longer truncated at a dot that was never a file extension.
- LDAP attribute conversion moved out of
Invoke-LDAPSearchinto its own function,ConvertFrom-LDAPAttribute. - The two attribute-name lists the conversion consults for every value are built once instead of per attribute.
- The domain policy interval arithmetic is now one function,
ConvertFrom-ADInterval. - The sidebar derives its category filter through the same function as the sections, and encodes names the way the rest of the report does.
- A finding card reports its section the way a reader sees it, and an untagged object card names a computer the same way a tagged one does.
- The HTML report is grouped into cards once instead of three times.
Securenow outranksHinteverywhere in the HTML report - the summary counts, the card badge and the scoring metadata.- HTML report risk score: the account tier classification now matches the identity gate every check already uses.
- Console attribute-column alignment is now computed in one place.
- Point and Print is no longer reported by
Get-GPORegistrySettings. - Group Policy Preferences
Registry.xmlparsing moved to a shared helper
Fixed
- ESC5 findings now carry the ESC5 write-up.
StrongCertificateBindingEnforcement=1deployed by Group Policy is reported.- ESC9 says what else has to be true.
- An EKU OID no longer matches a longer OID that starts with it.
- The reachability check reads a definition to its end.
- Both halves of the ESC14 check were deciding on a value that was never right.
- The Exchange service groups are judged, not just listed.
- An empty Exchange Windows Permissions group is reported.
netbootserveris no longer printed as a host name.- Two SCOM management servers without a DNS host name are both reported.
MemberCountis a number.Set-DomainUserandSet-DomainComputercould not read the userAccountControl they were about to change.- The per-OU list of unprotected computers is capped.
Test-AccountActivityunderstands the userAccountControl it is actually handed.- Domain controllers are out of the LAPS coverage figures.
- Exchange is found in the Configuration partition, not by a group's membership.
Get-InfrastructureServersno longer loses one half of the AD FS evidence to the other.- The two Computer checks read their findings back in one query instead of one per finding.
- A trust's SID filtering verdict now reads the direction and the kind of trust, not one bit.
- LDAP channel binding is read from
Registry.pol, not only fromGptTmpl.inf. Get-LDAPConfigurationasks the data layer which computers are domain controllers.- A GPO linked to the Domain Controllers OU is recognised as reaching them.
Get-GPOUserRightsAssignmentwas rebuilt around what Windows actually ships.- No GPO check asked whether the policy it was reading actually applies.
Get-GPOScheduledTaskscould mistake an ordinary account for SYSTEM.- The permission scans stopped at the organizational units.
Get-AddComputerRightslooked at the wrong container, and only at explicit grants.Get-LAPSPermissionscould not tell the two LAPS generations apart in its output.- Primary group membership was invisible everywhere.
adminCount=1was the definition of "privileged account", and it misses accounts.Get-AdminReversibleEncryptiononly looked at privileged accounts.Get-NonDefaultUserOwnersskipped disabled accounts.- Policy wording in a description hid the password written next to it.
Get-UnixPasswordAccountsonly looked at user objects.Get-UnixPasswordAccountsprinted the credential as decimal bytes.- Every server with protocol transition was listed as a Domain Controller.
- The advice on constrained delegation was wrong about what protects a Domain Controller.
- The template loaders threw instead of falling back to the current directory.
- Three gaps in the AD CS checks, found by auditing them against the published ESC preconditions.
- The certificate templates a CA publishes are listed one per line.
- A user right assigned to nobody was reported as held by the next line of the file.
- The BloodHound collector named its archive without saying where it put it.
- Every GPO was reported as "NOT LINKED", and linking a GPO would have destroyed the existing links on the target.
- Dates were read and written through the host's calendar, which broke Kerberos outright on some regional formats.
- Both offline report commands announced a report they had not written.
- A report that could not be built destroyed the report from the previous run.
Invoke-PasswordSpray -Auto's lockout protection never ran, and the console said the domain had no lockout policy while spraying every enabled account.- Every early exit in
Invoke-PasswordSpray'sbeginblock failed to stop the run. - An ACE granting full control via the SDDL generic bit (
GA,0x10000000) - the formInvoke-RBCDOperationitself writes, and the shape any real full-control grant in a DACL actually takes - was invisible toGet-OUPermissions.ps1(used byGet-LAPSPermissions,Get-PasswordResetRights,Get-DangerousOUPermissions) and toGet-PrivilegedGroupMembers's AdminSDHolder check. Compare-adPEASReport(scan-diff /-Baseline/-Current) silently dropped findings when two of them shared the same identity key, keeping only the last one.Get-ObjectTypeTitlesilently corrupted a report card's title whenever the underlying AD object's name contained a$.Get-CurrentUserTokenGroupsreturned an empty list, every time, for any user whosetokenGroupsheld exactly one SIDNew-DiamondTicket's encryption-type mismatch error named the wrong (generic) cipher label.- The same
Object[]-instead-of-byte[]return defect documented below for the PAC module (Golden/Silver/Diamond Ticket) also runs through the entire rest of the Kerberos stack: the ASN.1 DER encoder every AS-REQ/TGS-REQ/KRB-CRED byte stream is built from (Kerberos-ASN1.ps1), the crypto primitives that produce every NT-Hash/derived key/RC4-HMAC and AES-CTS ciphertext (Kerberos-Crypto.ps1), PKINIT's DH nonce/SHA-1/SHA-256/CMS-signature helpers, and one call site each in AS-REP-Roasting, Kerberoasting, ticket forging, and the main Kerberos auth flow. New-ASN1IntegerthrewInvalidCastExceptionfor any negative valueGet-NTHashFromPasswordexplicitly supports a blank password ([AllowEmptyString()]on-PlainPassword, forPASSWD_NOTREQDaccounts) but handed the resulting empty byte array toGet-MD4Hash, whose-Dataparameter wasMandatorywithout[AllowEmptyCollection()]and rejected it outright.- Every structure builder in the PAC (Golden/Silver/Diamond Ticket) module returned
Object[]where it documented, and its callers assumed,byte[]. LDAP_NO_SUCH_OBJECT(error 32) was classified as an error byConvertFrom-LDAPError/Get-ExceptionErrorInfo, but as the expected, non-error case byConvertFrom-HResult's equivalent entry (0x80072030) for the identical condition- A zero-length AV_PAIR in an NTLM Type2 (Challenge) message was decoded as one bogus character instead of an empty string.
New-RandomEPAIdentifier's suffix characters were not drawn uniformly.- PKINIT certificate authentication found the wrong identity, or none at all, on German-locale Windows.
- An ECDSA certificate's key size was reported as blank.
- A GPP
Registry.xmlwith no byte order mark was decoded wrongly on Windows PowerShell 5.1. REG_MULTI_SZfrom aRegistry.polwas reported as one string with null characters in it.- A ticket or certificate file whose name contains square brackets could not be read.
Export-adPEASFilewrote text with a BOM on Windows PowerShell 5.1 and without one on PowerShell 7.Search-Valueread square brackets in the search pattern as a wildcard character class.Test-AccountActivity -IncludeDetailsdropped every object on a second pass.- One LAPS password in 256 lost its update timestamp.
New-SafePassworddid not draw uniformly from its alphabet.- An ACE carrying a generic access right was displayed with an empty rights field.
- A resolved extended right was glued onto the right beside it.
ConvertTo-AccessRulesthrew on an empty attribute value- A
userParametersblob with exactly one Terminal Services setting was reported as a single letter. RECONNECT_SAMEled theCtxCfgFlags1flag list instead of closing it.- The DES key from
Primary:Kerberoswas never read. - An entry declaring a zero-length key produced two bytes of the blob as if they were key material.
- A single empty property discarded the credentials next to it.
WDigestHasheswas not always an array.Invoke-Kerberoastleft the caller's credential in session state.- gMSA passwords were never extracted at all in the built artifact.
- The NT hash of a gMSA password was computed from a mangled string.
- A Shadow Credential's device id was reported as a byte-reversed hex string.
- A disabled GPO link was reported as an active one, and enforcement was never seen.
- A recovered GPP password could be a password nobody has.
- One finding without a timestamp cost the reader the whole imported report.
- The worst password policy was the one the risk scoring could not read.
- A report converted from a JSON export could end with an exception.
- A directory object could inject script into the generated HTML report.
- A long attribute name ended an object's console output with an exception.
- A creation date without a trailing
Zwas read as the year 1601. - Access control entries silently disappeared when scanning a domain from outside it.
- A Domain Admin who was also a Backup Operator was reported as unprivileged.
Test-IsPrivilegeddid nothing useful when used from the pipeline.- A user principal name never resolved to a SID.
- A distinguished name with a comma or parentheses in its RDN resolved to nothing.
- A delegation inherited from the Certificate Templates container was invisible.
- An AD CS enrollment principal could be judged against another principal's SID.
- One failing query in
Get-InfrastructureServersended the whole check. - A password that could not be read was reported as readable.
- The Norwegian and Esperanto spellings of "password" were never matched.
- A credential mention in the description hid an assignment in the info attribute.
- An empty Exchange group was reported as having one member.
- A disabled account lockout was rated one step below a short password.
- A within-forest trust was told its disabled SID filtering was a security risk.
- The NetBIOS domain name was thrown away when the domain object could not be read.
- SCOM service accounts and security groups were introduced by the server help text.
- The SCCM and SCOM checks never reported a single server.
- Read-only domain controllers were missing from the infrastructure inventory.
- A current Windows Server was indistinguishable from a network printer.
Get-OutdatedComputers -InactiveDaysdid nothing.- The entire gMSA password-access analysis never ran.
- Every resource-based constrained delegation could disappear.
- A domain without AD CS produced a phantom vulnerable certificate template.
- A template enrollable through an All-Extended-Rights ACE was missed.
- Windows LAPS read delegations were never detected.
- Anonymous LDAP binding was reported as restricted when it was allowed.
- Scheduled tasks running as a named privileged account were never rated privileged.
- A failed SMB access was reported as a clean domain.
- A reported credential line was used as a wildcard pattern.
- The domain scan ignored PowerShell scripts.
- A quoted script path was mangled.
- A privileged user with a comma in their name was skipped.
- Restricted Groups entries keyed by group name were dropped.
- The severity of a GPO scheduled task and of a GPO registry setting never reached the report.
- A GPO ACE granting only delete rights was reported without a label
- Domain Users kept a blanket read finding that Authenticated Users and Everyone were exempt from.
- The LAPS check over-detected.
- A machine account quota of 0 depended on an accident.
- The minified and ultra-compressed builds shipped code the build itself had mangled.
- S4U delegation attacks reported a real success as a failure.
- The
Set-Domain*write helpers silently no-oped on writes that should have worked. - AD CS enrollment over RPC and HTTP lost errors and rejected valid input.
Invoke-DCSync -Identity <GUID>never reached the RPC call.Import-KerberosTicketcould not import a raw ticket.-ForceKerberoswas silently ignored for a cross-domain NetBIOS credential.-IgnoreSSLErrorsdemanded an explicit argument despite its name.- Smaller corrections: the SMB failure message in
Get-CredentialExposurewas printed twice; pattern hits in custom-path mode were tagged with the wrong object type; two tier-2 credential patterns were unreachable behind a more generic one; a scheduled task without a name attribute was reported asProperties; the membership path inGet-PrivilegedGroupMembersused a stray?as its separator; broad SIDs were compared with a substring regex instead of equality; andGet-DangerousOUPermissionsclaimed "no dangerous OU permissions detected" when the domain had returned no OUs at all.