Lab 2.3: Users, Permissions, and Logging
VMs Needed
- Windows
- Ubuntu
Lab Video
Windows Users, Permissions, and Logging
Objectives
- Practice the use of PowerShell for local user and group measurements.
- Explore how to gather permission information in Windows.
- Demonstrate how to extract information from the Windows audit logs.
- Examine PowerShell utilities available for querying Active Directory for information about objects like users, computers, and groups.
Lab Preparation
Boot the VMs required for the lab (see the "VMs Needed" section above) and log on to the Windows VM using the username student and the password student.
Part 1: Connect to Range VPN
Background: For this lab, you have a Windows Server 2022 domain controller available for testing. The server is hosted in AWS and requires a virtual private network (VPN) connection to reach it. In this section of the lab, you will connect to the AWS range VPN and test connectivity.
Instructions: Open the OpenVPN Connect program by double-clicking its icon on the Windows desktop.

Click on the Connect toggle to complete the connection to the VPN.

The toggle should turn green and the label should change from DISCONNECTED to CONNECTED to indicate that the connection was successful.
If you experience errors or the VPN fails to connect, let your instructor know about the issue to help you troubleshoot.
Finally, open Windows Terminal on the Windows VM and test your connection by pinging the domain controller:
ping windc.lab.local
Sample Results
PS > ping ubuntu
Pinging windc.lab.local [10.55.7.100] with 32 bytes of data:
Reply from 10.55.7.100: bytes=32 time=37ms TTL=127
Reply from 10.55.7.100: bytes=32 time=34ms TTL=127
Reply from 10.55.7.100: bytes=32 time=41ms TTL=127
Reply from 10.55.7.100: bytes=32 time=36ms TTL=127
Ping statistics for 10.55.7.100:
Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
Minimum = 34ms, Maximum = 41ms, Average = 37ms
Again, if you experience errors or the VPN fails to connect, let your instructor, TA, or online SME know about the issue to help you troubleshoot.
Close Windows Terminal because you will be working from an elevated privilege shell in the next section.
Part 2: Local Users and Groups
Preparation: Launch an Administrator PowerShell Core terminal on the Windows VM by double-clicking the "WT-Admin" icon on the desktop.

Environment Check
Before proceeding, ensure that you are working in an Administrator Windows Terminal in PowerShell Core.
![]()
Background: Even on domain-joined systems, you will often find issues with local users being given too much local privilege. On most Windows system audits, you will need to query local users and groups to ensure that least privilege is being considered.
Instructions: Determine which users are in the local administrators group on your Windows VM using PowerShell:
Get-LocalGroupMember -Group "administrators"
Sample Results
PS > Get-LocalGroupMember -Group "administrators"
ObjectClass Name PrincipalSource
----------- ---- ---------------
User Windows-99\rciadmin Local
User Windows-99\student Local
For class, we have made the student user a local administrator to make the labs work more easily. In an enterprise, your IT staff may do the same thing: make users local administrators so that they won't receive so many user support calls. This is an obvious violation of the principle of least privilege and should be noted in your audit report.
Another check you will often do on Windows system audits is to enumerate the users with various local privileges. This is unfortunately not easy to query with native tools, but we have provided a third-party PowerShell module to help accomplish the task. The module works by querying the Local Security Authority Server Service (LSASS) to retrieve or even set privileges assigned to users and groups.
Import the module and then view the commands it provides.
Import-Module C:\LabFiles\scripts\UserRights.psm1
Get-Command -Module UserRights
Sample Results
PS > Import-Module C:\LabFiles\scripts\UserRights.psm1
PS > Get-Command -Module UserRights
CommandType Name Version Source
----------- ---- ------- ------
Function Get-AccountsWithUserRight 0.0 UserRights
Function Get-UserRightsGrantedToAccount 0.0 UserRights
Function Grant-TokenPrivilege 0.0 UserRights
Function Grant-UserRight 0.0 UserRights
Function Revoke-TokenPrivilege 0.0 UserRights
Function Revoke-UserRight 0.0 UserRights
The Get-* functions are the ones that are safe for an auditor to use. One of the privileges we are often concerned with is the Debug Programs privilege. this privilege allows the user to read all memory addresses on the system and can be leveraged by attack tools like Mimikatz, which can steal encrypted credentials from memory.
To see who has been granted this privilege, you can use the Get-AccountsWithUserRight function provided by the module. Start by getting help for that command:
Get-Help Get-AccountsWithUserRight
Sample Results
PS > Get-Help Get-AccountsWithUserRight
NAME
Get-AccountsWithUserRight
SYNOPSIS
Gets all accounts that are assigned a specified privilege
SYNTAX
Get-AccountsWithUserRight [-Right] {SeTrustedCredManAccessPrivilege | SeNetworkLogonRight | SeTcbPrivilege |
SeMachineAccountPrivilege | SeIncreaseQuotaPrivilege | SeInteractiveLogonRight | SeRemoteInteractiveLogonRight |
SeBackupPrivilege | SeChangeNotifyPrivilege | SeSystemtimePrivilege | SeTimeZonePrivilege |
SeCreatePagefilePrivilege | SeCreateTokenPrivilege | SeCreateGlobalPrivilege | SeCreatePermanentPrivilege |
SeCreateSymbolicLinkPrivilege | SeDebugPrivilege | SeDenyNetworkLogonRight | SeDenyBatchLogonRight |
SeDenyServiceLogonRight | SeDenyInteractiveLogonRight | SeDenyRemoteInteractiveLogonRight |
SeEnableDelegationPrivilege | SeRemoteShutdownPrivilege | SeAuditPrivilege | SeImpersonatePrivilege |
SeIncreaseWorkingSetPrivilege | SeIncreaseBasePriorityPrivilege | SeLoadDriverPrivilege | SeLockMemoryPrivilege |
SeBatchLogonRight | SeServiceLogonRight | SeSecurityPrivilege | SeRelabelPrivilege | SeSystemEnvironmentPrivilege
| SeDelegateSessionUserImpersonatePrivilege | SeManageVolumePrivilege | SeProfileSingleProcessPrivilege |
SeSystemProfilePrivilege | SeUnsolicitedInputPrivilege | SeUndockPrivilege | SeAssignPrimaryTokenPrivilege |
SeRestorePrivilege | SeShutdownPrivilege | SeSyncAgentPrivilege | SeTakeOwnershipPrivilege} [-Computer <String>]
[-SidForUnresolvedName] [<CommonParameters>]
...
[Results Truncated]
The privilege you want to query for is called SeDebugPrivilege. Using the syntax given in the help file, query to see who has this right on the system.
Get-AccountsWithUserRight -Right SeDebugPrivilege
Sample Results
PS > Get-AccountsWithUserRight -Right SeDebugPrivilege
Right SID Account
----- --- -------
SeDebugPrivilege S-1-5-32-544 BUILTIN\Administrators
The privilege is granted to the local administrators group. This is not appropriate for most systems unless there is a business need for them to have the right. If there is no documented need, then your audit report should include this as a finding.
Note that a severe limitation of this module is that it does not recurse from groups to nested groups or users in its results. For example, you know from your testing above that the student user is in the local administrators group, yet if you query the rights assigned to student, you may be surprised by the (empty) results. Make sure you know the limitations of your tools and adjust your testing accordingly!
Get-UserRightsGrantedToAccount -Account student
Only by querying the right assigned to the administrators group will you be able to see the privileges being granted to student.
Get-UserRightsGrantedToAccount -Account Administrators
Sample Results
PS > Get-UserRightsGrantedToAccount -Account student
PS > Get-UserRightsGrantedToAccount -Account Administrators
Account Right
------- -----
Administrators SeSecurityPrivilege
Administrators SeBackupPrivilege
Administrators SeRestorePrivilege
Administrators SeSystemtimePrivilege
Administrators SeShutdownPrivilege
Administrators SeRemoteShutdownPrivilege
Administrators SeTakeOwnershipPrivilege
Administrators SeDebugPrivilege
Administrators SeSystemEnvironmentPrivilege
Administrators SeSystemProfilePrivilege
Administrators SeProfileSingleProcessPrivilege
Administrators SeIncreaseBasePriorityPrivilege
Administrators SeLoadDriverPrivilege
Administrators SeCreatePagefilePrivilege
Administrators SeIncreaseQuotaPrivilege
Administrators SeUndockPrivilege
Administrators SeManageVolumePrivilege
Administrators SeImpersonatePrivilege
Administrators SeCreateGlobalPrivilege
Administrators SeTimeZonePrivilege
Administrators SeCreateSymbolicLinkPrivilege
Administrators SeChangeNotifyPrivilege
Administrators SeDelegateSessionUserImpersonatePrivilege
Administrators SeInteractiveLogonRight
Administrators SeNetworkLogonRight
Administrators SeBatchLogonRight
Administrators SeRemoteInteractiveLogonRight
Part 3: File and Share Permissions
Environment Check
Before proceeding, ensure that you are working in an Administrator Windows Terminal in PowerShell Core.
![]()
Background: In this section of the lab, you will use PowerShell commands to view and analyze the access control lists for various Windows objects. These will include files and folders, shares, and registry keys.
Instructions: Begin by using the Get-ACL command to view the permissions set on the root directory of the Windows VM's C: drive:
Get-Acl C:\
Sample Results
PS > Get-Acl C:\
Directory:
Path Owner Access
---- ----- ------
C:\ NT SERVICE\TrustedInstaller NT AUTHORITY\Authenticated Users Allow AppendData…
You will probably notice that the output is not very useful in its default format. To view all of the properties returned by Get-Acl, run this command and take some time to examine the output for fields that might be useful to query:
Get-Acl C:\ | Get-Member -Type Properties
Sample Results
PS > Get-Acl C:\ | Get-Member -Type Properties
TypeName: System.Security.AccessControl.DirectorySecurity
Name MemberType Definition
---- ---------- ----------
Access CodeProperty System.Security.AccessControl.AuthorizationRuleCollection Access{get=GetAccess;}
CentralAccessPolicyId CodeProperty System.Security.Principal.SecurityIdentifier CentralAccessPolicyId{get=GetCentr…
Group CodeProperty System.String Group{get=GetGroup;}
Owner CodeProperty System.String Owner{get=GetOwner;}
Path CodeProperty System.String Path{get=GetPath;}
Sddl CodeProperty System.String Sddl{get=GetSddl;}
PSChildName NoteProperty string PSChildName=C:\
PSDrive NoteProperty PSDriveInfo PSDrive=C
PSParentPath NoteProperty string PSParentPath=
PSPath NoteProperty string PSPath=Microsoft.PowerShell.Core\FileSystem::C:\
PSProvider NoteProperty ProviderInfo PSProvider=Microsoft.PowerShell.Core\FileSystem
AccessRightType Property type AccessRightType {get;}
AccessRuleType Property type AccessRuleType {get;}
AreAccessRulesCanonical Property bool AreAccessRulesCanonical {get;}
AreAccessRulesProtected Property bool AreAccessRulesProtected {get;}
AreAuditRulesCanonical Property bool AreAuditRulesCanonical {get;}
AreAuditRulesProtected Property bool AreAuditRulesProtected {get;}
AuditRuleType Property type AuditRuleType {get;}
AccessToString ScriptProperty System.Object AccessToString {get=$toString = "";…
AuditToString ScriptProperty System.Object AuditToString {get=$toString = "";…
There are two properties that seem interesting: Owner and AccessToString. Try running this command to retrieve a simple report of the access control list entries on the c:\windows folder, using the AccessToString property. This property makes a readable list out of the PowerShell array of access control entries for the object being examined:
Get-Acl c:\windows | Format-List -Property PSChildName, Owner, AccessToString
Sample Results
PS > Get-Acl c:\windows | Format-List -Property PSChildName, Owner, AccessToString
PSChildName : Windows
Owner : NT SERVICE\TrustedInstaller
AccessToString : CREATOR OWNER Allow 268435456
NT AUTHORITY\SYSTEM Allow 268435456
NT AUTHORITY\SYSTEM Allow Modify, Synchronize
BUILTIN\Administrators Allow 268435456
BUILTIN\Administrators Allow Modify, Synchronize
BUILTIN\Users Allow -1610612736
BUILTIN\Users Allow ReadAndExecute, Synchronize
NT SERVICE\TrustedInstaller Allow 268435456
NT SERVICE\TrustedInstaller Allow FullControl
APPLICATION PACKAGE AUTHORITY\ALL APPLICATION PACKAGES Allow ReadAndExecute, Synchronize
APPLICATION PACKAGE AUTHORITY\ALL APPLICATION PACKAGES Allow -1610612736
APPLICATION PACKAGE AUTHORITY\ALL RESTRICTED APPLICATION PACKAGES Allow ReadAndExecute, Synchronize
APPLICATION PACKAGE AUTHORITY\ALL RESTRICTED APPLICATION PACKAGES Allow -1610612736
While this gives a nice list of access control entries for the folder, it also raises a new question: what do those numbers in the entries mean? It turns out that, much like the UserAccessControl field in Active Directory, Microsoft decided to use the bits of an integer to represent "generic access right" for files. The integer 268435456, which occurs in many of the access entries, is the "generic all" permission - effectively full control. The -1610612736 value represents a read, execute, and synchronize permission.
Similar permissions exist on registry keys. In PowerShell, the registry can be navigated like a filesystem, and permissions on registry keys can be queried like those on files or folders. Begin by changing your location to the HKLM:\Software registry key. Note how your PowerShell prompt changes when you change locations:
Set-Location HKLM:\SOFTWARE
Get-Acl HKLM:\SOFTWARE\ |
Format-List -Property PSChildName, Owner, AccessToString
Sample Results
PS > Set-Location HKLM:\SOFTWARE
PS > Get-Acl HKLM:\SOFTWARE\ |
>> Format-List -Property PSChildName, Owner, AccessToString
PSChildName : SOFTWARE
Owner : BUILTIN\Administrators
AccessToString : CREATOR OWNER Allow FullControl
NT AUTHORITY\SYSTEM Allow FullControl
BUILTIN\Administrators Allow FullControl
BUILTIN\Users Allow ReadKey
APPLICATION PACKAGE AUTHORITY\ALL APPLICATION PACKAGES Allow ReadKey
S-1-15-3-1024-1065365936-1281604716-3511738428-1654721687-432734479-3232135806-4053264122-3456934681
Allow ReadKey
On your own, feel free to explore the registry, navigating it like a filesystem, using commands like Get-Location, Set-Location, Change-Location, Get-ChildItem, and so on. When you have finished, change your location back to the root of the C: drive on the Windows VM:
Set-Location c:\
Checking for the existence of file shares and examining the associated permissions will be a part of many Windows audits. Remember from the lecture that the Get-FileShare cmdlet will return a list of shares associated with disks or filesystems. To view the file shares which exist on your Windows VM, use this command:
Get-FileShare
Sample Results
PS > Get-FileShare
Name HealthStatus OperationalStatus
---- ------------ -----------------
ADMIN$ Healthy Online
C$ Healthy Online
Note that there is a related command which will list all shares on the system, including non-file-shares, such as the IPC$ share created for inter-process communications. The command to list all Windows shares is Get-SMBShare. Run the Get-SMBShare command on your Windows VM, and compare the results. Note that this time, you see the IPC$ share, which is NOT associated with any filesystems.
Get-SMBShare
Sample Results
PS > Get-SMBShare
Name ScopeName Path Description
---- --------- ---- -----------
ADMIN$ * C:\Windows Remote Admin
C$ * C:\ Default share
IPC$ * Remote IPC
Part 4: Windows Logging
Environment Check
Before proceeding, ensure that you are working in an Administrator Windows Terminal in PowerShell Core.
![]()
Background: It is often helpful to examine the audit logs and their settings when conducting a review of a Windows system. In this section of the lab, you will use PowerShell to examine the windows audit logs.
Instructions: Begin by getting a list of the event logs on your Windows VM:
Get-WinEvent -ListLog *
Sample Results
PS > Get-WinEvent -ListLog *
LogMode MaximumSizeInBytes RecordCount LogName
------- ------------------ ----------- -------
Circular 15728640 2393 Windows PowerShell
Circular 20971520 2610 System
Circular 20971520 27452 Security
Circular 20971520 0 Key Management Service
Circular 1052672 0 Internet Explorer
Circular 20971520 0 HardwareEvents
Circular 20971520 3528 Application
...
[Results Truncated]
This gives a nice summary of the logs in use, their sizes, rotation methods, and the number of entries in each. Get-WinEvent can retrieve the events from a log when you specify the logName parameter.
Run this command to see the properties returned when you query the entries in one of the PowerShell logs:
Get-WinEvent -logname Microsoft-Windows-PowerShell/Operational |
Get-Member
Sample Results
PS > Get-WinEvent -logname Microsoft-Windows-PowerShell/Operational |
>> Get-Member
TypeName: System.Diagnostics.Eventing.Reader.EventLogRecord
Name MemberType Definition
---- ---------- ----------
Dispose Method void Dispose(), void IDisposable.Dispose()
Equals Method bool Equals(System.Object obj)
FormatDescription Method string FormatDescription(), string FormatDescription(System.Collections.Generic.IEnu…
GetHashCode Method int GetHashCode()
GetPropertyValues Method System.Collections.Generic.IList[System.Object] GetPropertyValues(System.Diagnostics…
GetType Method type GetType()
ToString Method string ToString()
ToXml Method string ToXml()
Message NoteProperty string Message=PowerShell console is ready for user input
ActivityId Property System.Nullable[guid] ActivityId {get;}
Bookmark Property System.Diagnostics.Eventing.Reader.EventBookmark Bookmark {get;}
ContainerLog Property string ContainerLog {get;}
Id Property int Id {get;}
Keywords Property System.Nullable[long] Keywords {get;}
KeywordsDisplayNames Property System.Collections.Generic.IEnumerable[string] KeywordsDisplayNames {get;}
Level Property System.Nullable[byte] Level {get;}
LevelDisplayName Property string LevelDisplayName {get;}
LogName Property string LogName {get;}
...
[Results Truncated]
To query for a specific type of event, you can use Where-object to specify things like which event log, event ID, machine name, and dates you are interested in. This example would retrieve all logon events (id #4624) from the security log on the local computer, within the last day, and will return only the five most recent:
Get-WinEvent -LogName security |
Where-Object {($_.Id -eq 4624) -and ($_.TimeCreated -gt (get-Date).AddDays(-1))} |
Select-Object TimeCreated, Id, Message -First 5 |
Format-List *
Sample Results
PS > Get-WinEvent -LogName security |
>> Where-Object {($_.Id -eq 4624) -and ($_.TimeCreated -gt (get-Date).AddDays(-1))} |
>> Select-Object TimeCreated, Id, Message -First 5 |
>> Format-List *
TimeCreated : 4/10/2024 12:22:01 AM
Id : 4624
Message : An account was successfully logged on.
Subject:
Security ID: S-1-5-18
Account Name: Windows$
Account Domain: WORKGROUP
Logon ID: 0x3E7
Logon Information:
Logon Type: 5
Restricted Admin Mode: -
Virtual Account: No
Elevated Token: Yes
Impersonation Level: Impersonation
New Logon:
Security ID: S-1-5-18
Account Name: SYSTEM
Account Domain: NT AUTHORITY
Logon ID: 0x3E7
...
[Results Truncated]
Part 5: Windows Domain Measurements
Environment Check
Before proceeding, ensure that you are working in an Administrator Windows Terminal in PowerShell Core.
![]()
Background: Measurements against Active Directory are an important part of many audits. In this section of the lab, you will use a number of PowerShell commands to query Active Directory.
Instructions: Begin by establishing a set of credentials to pass to the domain controller. Since you are not a member of the domain, you will have to specify a server and credentials to use for each query. If you were on your own domain, this would not be necessary.
Manual Active Directory Testing
Use these commands to establish credentials and then retrieve a list of all users in the domain. Note that the filter in this example is a PowerShell filter, not an LDAP filter. When prompted for a password, enter Password1.
$cred = Get-Credential -UserName student
Password1
Sample Results
PS > $cred = Get-Credential -UserName student
PowerShell credential request
Enter your credentials.
Password for user student: *********
Get-ADUser -Filter * -Server windc -Credential $cred
Sample Results
Get-ADUser -Filter * -Server windc -Credential $cred
DistinguishedName : CN=Administrator,CN=Users,DC=AUD1,DC=local
Enabled : True
GivenName :
Name : Administrator
ObjectClass : user
ObjectGUID : 5eecfdbc-db38-451e-ba77-a767c1252f2c
SamAccountName : Administrator
SID : S-1-5-21-2648094342-711214242-3780855676-500
Surname :
UserPrincipalName :
DistinguishedName : CN=Guest,CN=Users,DC=AUD1,DC=local
Enabled : False
GivenName :
Name : Guest
ObjectClass : user
ObjectGUID : 93d088c4-2fa9-4e3f-b500-02dba280e5d1
SamAccountName : Guest
SID : S-1-5-21-2648094342-711214242-3780855676-501
Surname :
UserPrincipalName :
...
[Results Truncated]
Active Directory implements the lightweight directory access protocol (LDAP), which means that you can use LDAP's query language to filter your searches. Use this command to query for all users with an LDAP filter. Note the prefix notation used for the & operator to find AD objects, which have a class of User and a category of Person.
Get-ADUser -LDAPFilter "(&(ObjectClass=User)(ObjectCategory=Person))" `
-Server windc -Credential $cred
Sample Results
PS > Get-ADUser -LDAPFilter "(&(ObjectClass=User)(ObjectCategory=Person))" `
>> -Server windc -Credential $cred
DistinguishedName : CN=Administrator,CN=Users,DC=AUD1,DC=local
Enabled : True
GivenName :
Name : Administrator
ObjectClass : user
ObjectGUID : 5eecfdbc-db38-451e-ba77-a767c1252f2c
SamAccountName : Administrator
SID : S-1-5-21-2648094342-711214242-3780855676-500
Surname :
UserPrincipalName :
DistinguishedName : CN=Guest,CN=Users,DC=AUD1,DC=local
Enabled : False
GivenName :
Name : Guest
ObjectClass : user
ObjectGUID : 93d088c4-2fa9-4e3f-b500-02dba280e5d1
SamAccountName : Guest
SID : S-1-5-21-2648094342-711214242-3780855676-501
Surname :
UserPrincipalName :
...
[Results Truncated]
The PowerShell filters allow for a lot of flexibility, and, in some cases, have syntax that removes the need for complicated LDAP filters. You'll try a few of them here. First, get a list of users who have at least one previous logon but have not logged in during the last 10 seconds. On a real audit against a real domain, looking for "stale users," you would look back for a number of days instead of seconds. In our lab, we have very few logon events, so we'll shorten the time frame to get some results:
$10Sec = (Get-Date).AddSeconds(-10)
Get-ADUser -Filter {LastLogonDate -le $10Sec} `
-Server windc -Credential $cred
Sample Results
PS > $10Sec = (Get-Date).AddSeconds(-10)
PS > Get-ADUser -Filter {LastLogonDate -le $10Sec} `
>> -Server windc -Credential $cred
DistinguishedName : CN=Student Auditor,OU=Guest Auditors,OU=Audit and Security,DC=AUD1,DC=local
Enabled : True
GivenName :
Name : Student Auditor
ObjectClass : user
ObjectGUID : cd35e53d-c595-4c7a-9204-08dfd5bd2688
SamAccountName : student
SID : S-1-5-21-2648094342-711214242-3780855676-2105
Surname :
UserPrincipalName :
Many of the queries that required binary math on the UserAccountControl attribute in older tools are often much easier to perform with Get-ADUser. Take these examples, which find users with no password required and no password expiration date. Think about whether the results that you see would indicate potential audit findings on a real audit.
Get-ADUser -Filter {PasswordNotRequired -eq $true} `
-Server windc -Credential $cred
Sample Results
PS > Get-ADUser -Filter {PasswordNotRequired -eq $true} `
>> -Server windc -Credential $cred
DistinguishedName : CN=Guest,CN=Users,DC=AUD1,DC=local
Enabled : False
GivenName :
Name : Guest
ObjectClass : user
ObjectGUID : 93d088c4-2fa9-4e3f-b500-02dba280e5d1
SamAccountName : Guest
SID : S-1-5-21-2648094342-711214242-3780855676-501
Surname :
UserPrincipalName :
DistinguishedName : CN=Skippie Torald,OU=Security Engineers,OU=Audit and Security,DC=AUD1,DC=local
Enabled : True
GivenName :
Name : Skippie Torald
ObjectClass : user
ObjectGUID : 18ec57ba-f9e8-44ff-88aa-d18162a551bb
SamAccountName : STorald
SID : S-1-5-21-2648094342-711214242-3780855676-1311
Surname :
UserPrincipalName :
Get-ADUser -Filter {PasswordNeverExpires -eq $true} `
-Server windc -Credential $cred
Sample Results
PS > Get-ADUser -Filter {PasswordNeverExpires -eq $true} `
>> -Server windc -Credential $cred
DistinguishedName : CN=Guest,CN=Users,DC=AUD1,DC=local
Enabled : False
GivenName :
Name : Guest
ObjectClass : user
ObjectGUID : 93d088c4-2fa9-4e3f-b500-02dba280e5d1
SamAccountName : Guest
SID : S-1-5-21-2648094342-711214242-3780855676-501
Surname :
UserPrincipalName :
DistinguishedName : CN=Lucilia Mell,OU=Server Admins,OU=Information Technology,DC=AUD1,DC=local
Enabled : True
GivenName :
Name : Lucilia Mell
ObjectClass : user
ObjectGUID : b3dd23fd-f45b-4fad-8fe2-351b7743731c
SamAccountName : LMell
SID : S-1-5-21-2648094342-711214242-3780855676-1807
Surname :
UserPrincipalName :
...
[Results Truncated]
PowerShell queries for domain group membership use the Get-ADGroupMember command. The -Recursive flag allows the query to show users who are part of nested groups, much like the "-expand" option in the older DSGet tool. View the members of the Domain Admins group using the command below. Think about whether the number and membership of domain administrators seems about right for an organization of around 1,000 users.
Get-ADGroupMember -Identity "Domain Admins" `
-Server windc -Credential $cred
Sample Results
PS > Get-ADGroupMember -Identity "Domain Admins" `
>> -Server windc -Credential $cred
distinguishedName : CN=Administrator,CN=Users,DC=AUD1,DC=local
name : Administrator
objectClass : user
objectGUID : 5eecfdbc-db38-451e-ba77-a767c1252f2c
SamAccountName : Administrator
SID : S-1-5-21-2648094342-711214242-3780855676-500
distinguishedName : CN=Allyn Badcock,OU=Server Admins,OU=Information Technology,DC=AUD1,DC=local
name : Allyn Badcock
objectClass : user
objectGUID : 1a732805-39a8-40af-acc0-565cafe53663
SamAccountName : ABadcock
SID : S-1-5-21-2648094342-711214242-3780855676-1921
...
[Results Truncated]
The command above does not recurse through nested groups, so it doesn't really give a full picture of the number of domain administrators. Try again using the -Recursive flag. Again, consider whether the results seem reasonable.
Get-ADGroupMember -Identity "Domain Admins" `
-Server windc -Credential $cred -Recursive
Sample Results
PS > Get-ADGroupMember -Identity "Domain Admins" `
>> -Server windc -Credential $cred -Recursive
distinguishedName : CN=Administrator,CN=Users,DC=AUD1,DC=local
name : Administrator
objectClass : user
objectGUID : 5eecfdbc-db38-451e-ba77-a767c1252f2c
SamAccountName : Administrator
SID : S-1-5-21-2648094342-711214242-3780855676-500
distinguishedName : CN=Allyn Badcock,OU=Server Admins,OU=Information Technology,DC=AUD1,DC=local
name : Allyn Badcock
objectClass : user
objectGUID : 1a732805-39a8-40af-acc0-565cafe53663
SamAccountName : ABadcock
SID : S-1-5-21-2648094342-711214242-3780855676-1921
...
[Results Truncated]
You will commonly collect the results of queries into CSV files either for your own later analysis or to give to the client with the report. Use this command to save a full list of domain administrators into a CSV for later use:
Get-ADGroupMember -Identity "Domain Admins" `
-Server windc -Credential $cred -Recursive |
Select-Object Name,SamAccountName |
Export-CSV "DomainAdmins.csv"
To view the contents of the file, run this command:
Get-Content DomainAdmins.csv
Sample Results
PS > Get-ADGroupMember -Identity "Domain Admins" `
>> -Server windc -Credential $cred -Recursive |
>> Select-Object Name,SamAccountName |
>> Export-CSV "DomainAdmins.csv"
PS > Get-Content DomainAdmins.csv
"Name","SamAccountName"
"Administrator","Administrator"
"Allyn Badcock","ABadcock"
"Student Auditor","student"
"Craggy Floyde","CFloyde"
"Hadley Waterworth","HWaterworth"
"Allsun Perigo","APerigo"
"Sonnie Stonebridge","SStonebridge"
"Yorke Lemanu","YLemanu"
"Adelina Bassilashvili","ABassilashvili"
"Waverley Grabb","WGrabb"
"Genny Cartmer","GCartmer"
...
[Results Truncated]
So far, you have queried groups to see what users are members. To view the groups to which a user belongs, you can use the unfortunately named Get-ADPrincipalGroupMembership command. Run this command to see the groups to which the student user belongs:
Get-ADPrincipalGroupMembership -Identity "student" `
-Server windc -Credential $cred
Sample Results
PS > Get-ADPrincipalGroupMembership -Identity "student" `
>> -Server windc -Credential $cred
distinguishedName : CN=Domain Users,CN=Users,DC=AUD1,DC=local
GroupCategory : Security
GroupScope : Global
name : Domain Users
objectClass : group
objectGUID : f35ad02f-885b-4e9b-ac4f-ee47ff5df986
SamAccountName : Domain Users
SID : S-1-5-21-2648094342-711214242-3780855676-513
distinguishedName : CN=Domain Admins,CN=Users,DC=AUD1,DC=local
GroupCategory : Security
GroupScope : Global
name : Domain Admins
objectClass : group
objectGUID : 56d59444-2fad-4b96-93a0-60c1a7b83079
SamAccountName : Domain Admins
SID : S-1-5-21-2648094342-711214242-3780855676-512
We normally would not expect to see the auditor's account provisioned as a domain administrator! Like many administrative teams, we made the decision to add the auditor to this group so "everything will work" for them. Feel free to include a recommendation in your report if this seems inappropriate!
Unfortunately, the Get-ADPrincipalGroupMembership command does not expand the group membership the way that the older DSGet tool does. Your options are to write a small script to recursively list the groups or simply use DSGet for that function. We remind you again that knowing how to use all the tools available to the auditor can make you much more efficient and effective.
To use DSGet, you will need to know the distinguished name (DN) of the auditor user. You can discover the DN simply by running Get-ADUser with the username you are interested in as the Identity parameter. The entire string highlighted in the screenshot below is the DN.
Get-ADUser -Identity student -Server windc -Credential $cred
Sample Results
PS > Get-ADUser -Identity student -Server windc -Credential $cred
DistinguishedName : CN=Student Auditor,OU=Guest Auditors,OU=Audit and Security,DC=AUD1,DC=local
Enabled : True
GivenName :
Name : Student Auditor
ObjectClass : user
ObjectGUID : cd35e53d-c595-4c7a-9204-08dfd5bd2688
SamAccountName : student
SID : S-1-5-21-2648094342-711214242-3780855676-2105
Surname :
UserPrincipalName :
To avoid typing errors, we usually capture the DN of the user we are testing to a variable and then pass that variable in the DSGet command line. Run this command and note the difference in the lists returned by DSGet and Get-ADPrincipalGroupMembership.
$userDN = (Get-ADUser -Identity student `
-Server windc -Credential $cred).DistinguishedName
dsget user "$userDN" -memberof -expand -s windc -u student -p Password1
Sample Results
PS > $userDN = (Get-ADUser -Identity student `
>> -Server windc -Credential $cred).DistinguishedName
PS > dsget user "$userDN" -memberof -expand -s windc -u student -p Password1
"CN=Domain Admins,CN=Users,DC=AUD1,DC=local"
"CN=Domain Users,CN=Users,DC=AUD1,DC=local"
"CN=Denied RODC Password Replication Group,CN=Users,DC=AUD1,DC=local"
"CN=Schema Admins,CN=Users,DC=AUD1,DC=local"
"CN=Administrators,CN=Builtin,DC=AUD1,DC=local"
"CN=Users,CN=Builtin,DC=AUD1,DC=local"
Scripted Active Directory Testing
There are several measurements you will find yourself routinely taking in Active Directory environments, which makes these measurements perfect for taking with a script! In the author's audit practice, we have standard scripts that we run for data gathering. We have included a simple example in the lab files repository.
The ADAuditGeneric.ps1 script will gather basic demographic information about the AD. Then it will check for common user account misconfigurations and indicators of "orphaned" user accounts. Change to the lab scripts directory, and run the script with these commands:
Set-Location C:\LabFiles\scripts\
.\ADAuditGeneric.ps1 -Server windc -Credential $cred
Sample Results
PS > Set-Location C:\LabFiles\scripts\
PS > .\ADAuditGeneric.ps1 -Server windc -Credential $cred
NetBIOSName : AUD1
DNSRoot : aud1.local
Forest : aud1.local
ADFunctionalLevel : Windows2016Domain
EnabledUsers : 995
DisabledUsers : 11
TotalUsers : 1006
StalePasswordUsers : 0
InactiveUsers : 994
ActiveUsers : 995
DomainAdmins : 71
SchemaAdmins : 71
EnterpriseAdmins : 1
PasswordNeverExpires : 8
PasswordNeverSet : 0
PasswordNotRequired : 1
Live Data in Use!
Remember that your data may differ from these results!
Note that the script has saved several CSV files with the results of many of the queries it has run. On your own, you can view the CSV files in the console with the Get-Content cmdlet or open them in OpenOffice if you would like.
Get-ChildItem *.csv
Sample Results
PS > Get-ChildItem *.csv
Directory: C:\LabFiles\scripts
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 4/10/2024 12:44 AM 2417 DisabledUsers.csv
-a--- 4/10/2024 12:44 AM 14683 DomainAdmins.csv
-a--- 4/10/2024 12:44 AM 251 EnterpriseAdmins.csv
-a--- 4/10/2024 12:44 AM 254104 InactiveUsers.csv
-a--- 4/10/2024 12:44 AM 1830 NonExpiringPwdUsers.csv
-a--- 4/10/2024 12:44 AM 14683 SchemaAdmins.csv
-a--- 4/10/2024 12:44 AM 254152 StalePasswordUsers.csv
When you have finished, close the administrative terminal session you have open.
Disconnect OpenVPN Client
When you have finished with the lab, you can disconnect the VPN by toggling the CONNECTED slider in the OpenVPN Connect application.

Click the Confirm button to continue disconnecting.
