ActiveDirectoryStig.psm1
Function Set-ActiveDirectoryStigItems { <# .SYNOPSIS Executes all of the Active Directory STIG settings included in the module. .DESCRIPTION The cmdlet runs each audit and configuration setting in the module. It covers items in both the Windows Server Domain Controller STIG and Active Directory STIG. The cmdlet must be run with Enterprise Admin credentials since it sets the configuration for each domain in the entire forest of the current user. .PARAMETER Credential The credentials to run the cmdlet with. .INPUTS System.Management.Automation.PSCredential The input can be piped to Set-ActiveDirectoryStigItems .OUTPUTS None .EXAMPLE Set-ActiveDirectoryStigItems Configures all of the settings in this module. .EXAMPLE Set-ActiveDirectoryStigItems -Credential (Get-Credential) Configures all of the settings in this module using the specified credentials. .NOTES This cmdlet must be run with enterprise admin credentials. AUTHOR: Michael Haken LAST UPDATE: 2/27/2016 #> [CmdletBinding()] Param( [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credential = [System.Management.Automation.PSCredential]::Empty ) Begin { if ($Credential -eq $null) { $Credential = [System.Management.Automation.PSCredential]::Empty } if (!(Test-IsEnterpriseAdmin -Credential $Credential)) { Write-Warning "The cmdlet must be run with Enterprise Admin credentials." Exit 1 } try { Import-Module ActiveDirectory } catch [Exception] { Write-Warning "The Active Directory module is required to use this module." Exit 1 } } Process { Set-RIDManagerAuditing -Credential $Credential Set-PolicyContainerAuditing -Credential $Credential Set-MaxConnectionIdleTime -Credential $Credential Set-InfrastructureObjectAuditing -Credential $Credential Set-DsHeuristics -Credential $Credential Set-AdminSDHolderAuditing -Credential $Credential Set-DomainAuditing -Credential $Credential Set-DomainControllersOUAuditing -Credential $Credential Set-NTDSFilePermissions -Credential $Credential } End { } } Function Set-NTDSFilePermissions { <# .SYNOPSIS Active Directory data files must have proper access control permissions. .DESCRIPTION The Set-NTDSFilePermissions cmdlet sets the required security permissions for the database files and log files. The command must be run with Enterprise Admin credentials. .PARAMETER Credential The credentials to use to make the change. The command must be run with Enterprise Admin credentials. .EXAMPLE Set-NTDSFilePermissions Configures the required permissions for the NTDS database and logs .INPUTS System.Management.Automation.PSCredential .OUTPUTS None .NOTES AUTHOR: Michael Haken LAST UPDATE: 2/27/2016 .FUNCTIONALITY STIG Windows Server 2012 / 2012 R2 Domain Controller V2R3 STIG ID WN12-AD-000001-DC Rule ID SV-51175r2 Vuln ID V-8316 Severity CAT I #> [CmdletBinding()] Param( [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credential = [System.Management.Automation.PSCredential]::Empty ) Begin { if ($Credential -eq $null) { $Credential = [System.Management.Automation.PSCredential]::Empty } if (!(Test-IsEnterpriseAdmin -Credential $Credential)) { Write-Error "The cmdlet must be run with Enterprise Admin credentials." Exit 1 } $BuiltinAdministrators = New-Object Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::BuiltinAdministratorsSid, $null) $System = New-Object Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::LocalSystemSid, $null) $CreatorOwner = New-Object Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::CreatorOwnerSid, $null) $LocalService = New-Object Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::LocalServiceSid, $null) $AdministratorAce = New-Object System.Security.AccessControl.FileSystemAccessRule($BuiltinAdministrators, [System.Security.AccessControl.FileSystemRights]::FullControl, @([System.Security.AccessControl.InheritanceFlags]::ObjectInherit, [System.Security.AccessControl.InheritanceFlags]::ContainerInherit), [System.Security.AccessControl.PropagationFlags]::None, [System.Security.AccessControl.AccessControlType]::Allow ) $SystemAce = New-Object System.Security.AccessControl.FileSystemAccessRule($System, [System.Security.AccessControl.FileSystemRights]::FullControl, @([System.Security.AccessControl.InheritanceFlags]::ObjectInherit, [System.Security.AccessControl.InheritanceFlags]::ContainerInherit), [System.Security.AccessControl.PropagationFlags]::None, [System.Security.AccessControl.AccessControlType]::Allow ) $CreatorOwnerAce = New-Object System.Security.AccessControl.FileSystemAccessRule($CreatorOwner, [System.Security.AccessControl.FileSystemRights]::FullControl, @([System.Security.AccessControl.InheritanceFlags]::ObjectInherit, [System.Security.AccessControl.InheritanceFlags]::ContainerInherit), [System.Security.AccessControl.PropagationFlags]::None, [System.Security.AccessControl.AccessControlType]::Allow ) $LocalServiceAce = New-Object System.Security.AccessControl.FileSystemAccessRule($LocalService, @([System.Security.AccessControl.FileSystemRights]::AppendData, [System.Security.AccessControl.FileSystemRights]::CreateDirectories), [System.Security.AccessControl.InheritanceFlags]::ContainerInherit, [System.Security.AccessControl.PropagationFlags]::None, [System.Security.AccessControl.AccessControlType]::Allow ) } Process { ([System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()).Domains | ForEach-Object { $_.DomainControllers | Select-Object -ExpandProperty Name | ForEach-Object { Write-Host ("Reviewing Domain Contoller " + $_) $NTDS = Invoke-Command -ScriptBlock { Get-ItemProperty -Path "HKLM:\\System\\CurrentControlSet\\Services\\NTDS\\Parameters" } -ComputerName $_ $DSA = $NTDS.'DSA Database File' $Logs = $NTDS.'Database log files path' $DSA = "\\$_\\" + $DSA.Replace(":\","$\").Replace("\", "\\") $Logs = "\\$_\\" + $Logs.Replace(":\","$\").Replace("\", "\\") $DSA = $DSA.Substring(0, $DSA.LastIndexOf("\\")) $ACL1 = Get-Acl -Path $DSA foreach ($Rule in $ACL1.Access) { $ACL1.RemoveAccessRule($Rule) | Out-Null } $ACL1.AddAccessRule($AdministratorAce) $ACL1.AddAccessRule($SystemAce) Write-Host "Setting $DSA ACL" Set-Acl -Path $DSA -AclObject $ACL1 Get-ChildItem -Path $DSA | ForEach-Object { $Acl = Get-Acl -Path $_.FullName foreach ($Rule in $Acl.Access) { if (-not $Rule.IsInherited) { $Acl.RemoveAccessRule($Rule) | Out-Null } } Set-Acl -Path $_.FullName -AclObject $Acl } $ACL2 = Get-Acl -Path $Logs foreach ($Rule in $ACL2.Access) { $ACL2.RemoveAccessRule($Rule) | Out-Null } $ACL2.AddAccessRule($AdministratorAce) $ACL2.AddAccessRule($SystemAce) $ACL2.AddAccessRule($LocalServiceAce) $ACL2.AddAccessRule($CreatorOwnerAce) Write-Host "Setting $Logs ACL" Set-Acl -Path $Logs -AclObject $ACL2 Get-ChildItem -Path $Logs | ForEach-Object { $Acl = Get-Acl -Path $_.FullName foreach ($Rule in $Acl.Access) { if (-not $Rule.IsInherited) { $Acl.RemoveAccessRule($Rule) | Out-Null } } Set-Acl -Path $_.FullName -AclObject $Acl } } } } End{} } Function Set-RIDManagerAuditing { <# .SYNOPSIS The Active Directory RID Manager$ object must be configured with proper audit settings. .DESCRIPTION The Set-RIDManagerAuditing cmdlet sets the required auditing. The command must be run with Enterprise Admin credentials. .PARAMETER Credential The credentials to use to make the change. The command must be run with Enterprise Admin credentials. .EXAMPLE Set-RIDManagerAuditing Configures the required auditing for the RID Manager object. .INPUTS System.Management.Automation.PSCredential .OUTPUTS None .NOTES AUTHOR: Michael Haken LAST UPDATE: 2/27/2016 .FUNCTIONALITY STIG Windows Server 2012 / 2012 R2 Domain Controller V2R3 STIG ID WN12-AU-000212-DC Rule ID SV-51174r2 Vuln ID V-39330 Severity CAT II #> [CmdletBinding()] Param( [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credential = [System.Management.Automation.PSCredential]::Empty ) Begin { if ($Credential -eq $null) { $Credential = [System.Management.Automation.PSCredential]::Empty } if (!(Test-IsEnterpriseAdmin -Credential $Credential)) { Write-Error "The cmdlet must be run with Enterprise Admin credentials." Exit 1 } } Process { $Domains = Get-ForestDomains foreach ($Domain in $Domains) { [System.DirectoryServices.ActiveDirectoryAuditRule[]] $Rules = New-RIDManagerAuditRuleSet Set-Auditing -Domain $Domain -Rules $Rules -ObjectCN "CN=RID Manager$,CN=System" } } End {} } Function Set-PolicyContainerAuditing { <# .SYNOPSIS Active Directory Group Policy objects must be configured with proper audit settings. .DESCRIPTION The Set-PolicyContainerAuditing cmdlet sets the required auditing. The command must be run with Enterprise Admin credentials. .PARAMETER Credential The credentials to use to make the change. The command must be run with Enterprise Admin credentials. .EXAMPLE Set-PolicyContainerAuditing Configures the required auditing for the Group Policy container. .INPUTS None .OUTPUTS None .NOTES AUTHOR: Michael Haken LAST UPDATE: 2/27/2016 .FUNCTIONALITY STIG Windows Server 2012 / 2012 R2 Domain Controller V2R3 STIG ID WN12-AU-000207-DC Rule ID SV-51169r4 Vuln ID V-39325 Severity CAT II #> [CmdletBinding()] Param( [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credential = [System.Management.Automation.PSCredential]::Empty ) Begin { if ($Credential -eq $null) { $Credential = [System.Management.Automation.PSCredential]::Empty } if (!(Test-IsEnterpriseAdmin -Credential $Credential)) { Write-Error "The cmdlet must be run with Enterprise Admin credentials." Exit 1 } } Process { $Domains = Get-ForestDomains foreach ($Domain in $Domains) { [System.DirectoryServices.ActiveDirectoryAuditRule[]] $Rules = New-PolicyContainerAuditRuleSet Set-Auditing -Domain $Domain -Rules $Rules -ObjectCN "CN=Policies,CN=System" } } End {} } Function Set-MaxConnectionIdleTime { <# .SYNOPSIS The directory service must be configured to terminate LDAP-based network connections to the directory server after five (5) minutes of inactivity. .DESCRIPTION The Set-MaxConnectionIdleTime cmdlet sets the timeout for inactive connections. The command must be run with Enterprise Admin credentials. .PARAMETER MaxConnIdleTime The timeout for inactive network connections. Defaults to 5 minutes. .PARAMETER Credential The credentials to use to make the change. The command must be run with Enterprise Admin credentials. .EXAMPLE Set-MaxConnectionIdleTime Sets the connection idle time setting to 5 minutes (default). .EXAMPLE Set-MaxConnectionIdleTime -MaxConnIdleTime 180 Sets the connection idle time setting to 3 minutes .INPUTS System.Int32, System.Management.Automation.PSCredential .OUTPUTS System.String .NOTES AUTHOR: Michael Haken LAST UPDATE: 2/27/2016 .FUNCTIONALITY STIG Windows Server 2012 / 2012 R2 Domain Controller V2R3 STIG ID WN12-AD-000014-DC Rule ID SV-51188r2 Vuln ID V-14831 Severity CAT III #> [CmdletBinding()] Param ( [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] [int]$MaxConnIdleTime = 300, [Parameter(Position=1,ValueFromPipelineByPropertyName=$true)] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credential = [System.Management.Automation.PSCredential]::Empty ) Begin { if ($Credential -eq $null) { $Credential = [System.Management.Automation.PSCredential]::Empty } if (!(Test-IsEnterpriseAdmin -Credential $Credential)) { Write-Warning "The cmdlet must be run with Enterprise Admin credentials." Exit 1 } if ($MaxConnIdleTime -lt 1) { Write-Warning "The max connection idle time must be greater than 0." Exit 1 } } Process { [string]$DomainDN = [System.String]::Empty if ([System.Security.Principal.WindowsIdentity]::GetCurrent().IsSystem) { $Role = Get-WmiObject -Class Win32_OperatingSystem -Property ProductType | Select-Object -ExpandProperty ProductType if ($Role -eq 2) { $DomainDN = Get-ADDomain -Identity (Get-ADForest -Current LoggedOnUser -Server $env:COMPUTERNAME).RootDomain -Server $env:COMPUTERNAME | Select-Object -ExpandProperty DistinguishedName } } if ([System.String]::IsNullOrEmpty($DomainDN)) { $DomainDN = Get-ADDomain -Identity (Get-ADForest -Current LoggedOnUser).RootDomain | Select-Object -ExpandProperty DistinguishedName } [string]$SearchBase = "CN=Query-Policies,CN=Directory Service,CN=Windows NT,CN=Services,CN=Configuration," + $DomainDN [Microsoft.ActiveDirectory.Management.ADEntity]$Policies = get-adobject -SearchBase $SearchBase -Filter 'ObjectClass -eq "queryPolicy" -and Name -eq "Default Query Policy"' -Properties * $AdminLimits = [Microsoft.ActiveDirectory.Management.ADPropertyValueCollection]$Policies.lDAPAdminLimits for ($i = 0; $i -lt $AdminLimits.Count; $i++) { if ($AdminLimits[$i] -match "MaxConnIdleTime=*") { break } } if ($i -lt $AdminLimits.Count) { $AdminLimits[$i] = "MaxConnIdleTime=$MaxConnIdleTime" } else { $AdminLimits.Add("MaxConnIdleTime=$MaxConnIdleTime") } if ($Credential -ne [System.Management.Automation.PSCredential]::Empty -and $Credential -ne $null) { Set-ADObject -Identity $Policies -Clear lDAPAdminLimits -Credential $Credential foreach ($Limit in $AdminLimits) { Set-ADObject -Identity $Policies -Add @{lDAPAdminLimits=$Limit} -Credential $Credential } } else { Set-ADObject -Identity $Policies -Clear lDAPAdminLimits foreach ($Limit in $AdminLimits) { Set-ADObject -Identity $Policies -Add @{lDAPAdminLimits=$Limit} } } } End { Write-Output (Get-ADObject -Identity $Policies -Properties * | Select-Object -ExpandProperty lDAPAdminLimits | Where-Object {$_ -match "MaxConnIdleTime=*"}) } } Function Set-InfrastructureObjectAuditing { <# .SYNOPSIS The Active Directory Infrastructure object must be configured with proper audit settings. .DESCRIPTION The Set-InfrastructureObjectAuditing cmdlet sets the required auditing. The command must be run with Enterprise Admin credentials. .PARAMETER Credential The credentials to use to make the change. The command must be run with Enterprise Admin credentials. .EXAMPLE Set-InfrastructureObjectAudting Configures the required auditing for the infrastructure object. .INPUTS System.Management.Automation.PSCredential .OUTPUTS None .NOTES AUTHOR: Michael Haken LAST UPDATE: 12/5/2015 .FUNCTIONALITY STIG Windows Server 2012 / 2012 R2 Domain Controller V2R3 STIG ID WN12-AU-000209-DC Rule ID SV-51171r2 Vuln ID V-39327 Severity CAT II #> [CmdletBinding()] Param( [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credential = [System.Management.Automation.PSCredential]::Empty ) Begin { if ($Credential -eq $null) { $Credential = [System.Management.Automation.PSCredential]::Empty } if (!(Test-IsEnterpriseAdmin -Credential $Credential)) { Write-Error "The cmdlet must be run with Enterprise Admin credentials." Exit 1 } } Process { $Domains = Get-ForestDomains foreach ($Domain in $Domains) { [System.DirectoryServices.ActiveDirectoryAuditRule[]] $Rules = New-InfrastructureObjectAuditRuleSet Set-Auditing -Domain $Domain -Rules $Rules -ObjectCN "CN=Infrastructure" } } End {} } Function Set-DsHeuristics { <# .SYNOPSIS The dsHeuristics option can be configured to override the default restriction on anonymous access to AD data above the rootDSE level. Directory data (outside the root DSE) of a non-public directory must be configured to prevent anonymous access. .DESCRIPTION The Set-DsHeuristics cmdlet configures anonymous access to AD data. The command must be run with Enterprise Admin credentials. .PARAMETER AddAnonymousAccess Adds anonymous read access to the AD Forest .PARAMETER Credential The credentials to use to make the change. The command must be run with Enterprise Admin credentials. .EXAMPLE Set-DsHeuristic Removes anonymous access from the AD Forest .EXAMPLE Set-DsHeuristic -AddAnonymousRead Adds anonymous read access to the AD Forest .INPUTS System.Management.Automation.SwitchParameter, System.Management.Automation.PSCredential .OUTPUTS System.String .NOTES AUTHOR: Michael Haken LAST UPDATE: 2/27/2016 .FUNCTIONALITY STIG Active Directory Forest V2R5 1/23/2015 Windows Server 2012 / 2012 R2 Domain Controller V2R3 STIG ID AD.0230 WN12-AD-000013-DC Rule ID SV-9052r2 SV-52838r1 Vuln ID V-8555 V-1070 Severity CAT II #> [CmdletBinding()] Param ( [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] [switch]$AddAnonymousRead = $false, [Parameter(Position=1,ValueFromPipelineByPropertyName=$true)] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credential = [System.Management.Automation.PSCredential]::Empty ) Begin { if ($Credential -eq $null) { $Credential = [System.Management.Automation.PSCredential]::Empty } if (!(Test-IsEnterpriseAdmin -Credential $Credential)) { Write-Error "The cmdlet must be run with Enterprise Admin credentials." Exit 1 } try { Import-Module ActiveDirectory } catch [Exception] { Write-Warning "This cmdlet requires the Active Directory module." Exit 1 } } Process { $DN = ("CN=Directory Service,CN=Windows NT,CN=Services,CN=Configuration," + (Get-ADDomain -Identity (Get-ADForest -Current LocalComputer).RootDomain).DistinguishedName) $DirectoryService = Get-ADObject -Identity $DN -Properties dsHeuristics [string]$Heuristic = $DirectoryService.dsHeuristics [array]$Array = @() if ($AddAnonymousRead) { if($Heuristic -ne $null -and $Heuristic -ne [System.String]::Empty) { $Array = $Heuristic.ToCharArray() if ($Array.Length -lt 7) { for ($i = $Array.Length; $i -lt 6; $i++) { $Array += "0" } $Array += "2" } else { $Array[6] = "2" } } else { $Array = "0000002" } } else { if (($Heuristic -ne $null) -and ($Heuristic -ne [System.String]::Empty) -and ($Heuristic.Length -ge 7)) { $Array = $Heuristic.ToCharArray() $Array[6] = "0"; } else { $Array = "0000000" } } [string]$Heuristic = "$Array".Replace(" ", [System.String]::Empty) if ($Heuristic -ne $null -and $Heuristic -ne [System.String]::Empty) { if ($Credential -ne [System.Management.Automation.PSCredential]::Empty -and $Credential -ne $null) { Set-ADObject -Identity $DirectoryService -Replace @{dsHeuristics = $Heuristic} -Credential $Credential } else { Set-ADObject -Identity $DirectoryService -Replace @{dsHeuristics = $Heuristic} } } } End { $Result = Get-ADObject -Identity $DirectoryService -Properties dsHeuristics | Select-Object -ExpandProperty dsHeuristics if ($Result -ne $null) { Write-Output ("dsHeuristics: " + $Result) } else { Write-Warning "dsHeuristics is not set" Exit 1 } } } Function Set-DomainControllersOUAuditing { <# .SYNPOSIS The Active Directory Domain Controllers Organizational Unit (OU) object must be configured with proper audit settings. .DESCRIPTION The Set-DomainControllersOUAuditing cmdlet sets the required auditing. The command must be run with Enterprise Admin credentials. .PARAMETER Credential The credentials to use to make the change. The command must be run with Enterprise Admin credentials. .EXAMPLE Set-DomainControllersOUAuditing Configures the required auditing for the domain controller OU. .INPUTS System.Management.Automation.PSCredential .OUTPUTS None .NOTES AUTHOR: Michael Haken LAST UPDATE: 2/27/2016 .FUNCTIONALITY STIG Windows Server 2012 / 2012 R2 Domain Controller V2R3 STIG ID WN12-AU-000210-DC Rule ID SV-51172r2 Vuln ID V-39328 Severity CAT II #> [CmdletBinding()] Param( [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credential = [System.Management.Automation.PSCredential]::Empty ) Begin { if ($Credential -eq $null) { $Credential = [System.Management.Automation.PSCredential]::Empty } if (!(Test-IsEnterpriseAdmin -Credential $Credential)) { Write-Error "The cmdlet must be run with Enterprise Admin credentials." Exit 1 } } Process { $Domains = Get-ForestDomains foreach ($Domain in $Domains) { [System.DirectoryServices.ActiveDirectoryAuditRule[]] $Rules = New-DomainControllersAuditRuleSet Set-Auditing -Domain $Domain -Rules $Rules -ObjectCN "OU=Domain Controllers" } } End {} } Function Set-DomainAuditing { <# .SYNOPSIS The Active Directory Domain object must be configured with proper audit settings. .DESCRIPTION The Set-DomainAuditing cmdlet sets the required auditing. The command must be run with Enterprise Admin credentials. .PARAMETER Credential The credentials to use to make the change. The command must be run with Enterprise Admin credentials. .EXAMPLE Set-DomainAudting Configures the required auditing for the domain .INPUTS System.Management.Automation.PSCredential .OUTPUTS None .NOTES AUTHOR: Michael Haken LAST UPDATE: 2/27/2016 .FUNCTIONALITY STIG Windows Server 2012 / 2012 R2 Domain Controller V2R3 STIG ID WN12-AU-000208-DC Rule ID SV-51170r2 Vuln ID V-39326 Severity CAT II #> [CmdletBinding()] Param( [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credential = [System.Management.Automation.PSCredential]::Empty ) Begin { if ($Credential -eq $null) { $Credential = [System.Management.Automation.PSCredential]::Empty } if (!(Test-IsEnterpriseAdmin -Credential $Credential)) { Write-Error "The cmdlet must be run with Enterprise Admin credentials." Exit 1 } } Process { $Domains = Get-ForestDomains foreach ($Domain in $Domains) { [System.DirectoryServices.ActiveDirectoryAuditRule[]] $Rules = New-DomainAuditRuleSet -DomainSID (Get-ADDomain -Identity $Domain | Select-Object -ExpandProperty DomainSID) Set-Auditing -Domain $Domain -Rules $Rules -ObjectCN "" } } End {} } Function Set-AdminSDHolderAuditing { <# .SYNOPSIS The Active Directory AdminSDHolder object must be configured with proper audit settings. .DESCRIPTION The Set-AdminSDHolderAuditing cmdlet sets the required auditing. The command must be run with Enterprise Admin credentials. .PARAMETER Credential The credentials to use to make the change. The command must be run with Enterprise Admin credentials. .EXAMPLE Set-AdminSDHolderAuditing Configures the required auditing for the AdminSDHolder object. .INPUTS System.Management.Automation.PSCredential .OUTPUTS None .NOTES AUTHOR: Michael Haken LAST UPDATE: 2/27/2016 .FUNCTIONALITY STIG Windows Server 2012 / 2012 R2 Domain Controller V2R3 STIG ID WN12-AU-000211-DC Rule ID SV-51173r2 Vuln ID V-39329 Severity CAT II #> [CmdletBinding()] Param( [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credential = [System.Management.Automation.PSCredential]::Empty ) Begin { if ($Credential -eq $null) { $Credential = [System.Management.Automation.PSCredential]::Empty } if (!(Test-IsEnterpriseAdmin -Credential $Credential)) { Write-Error "The cmdlet must be run with Enterprise Admin credentials." Exit 1 } } Process { $Domains = Get-ForestDomains foreach ($Domain in $Domains) { [System.DirectoryServices.ActiveDirectoryAuditRule[]] $Rules = New-EveryoneAuditRuleSet Set-Auditing -Domain $Domain -Rules $Rules -ObjectCN "CN=AdminSDHolder,CN=System" } } End{} } Function Set-Auditing { <# .SYNOPSIS Sets auditing on an Active Directory object. .DESCRIPTION The Set-Auditing cmdlet applies an audit rule set to an AD object. .PARAMETER Domain The domain to set the auditing in. .PARAMETER ObjectCN The CN of the object to set auditing on up to the domain part of the DN. This can be an emptry string to set auditing on the domain. .PARAMETER Rules The array of ActiveDirectoryAuditRule. .EXAMPLE Set-Auditing -Domain contoso.com -ObjectCN "CN=Policies,CN=System" -Rules $Rules Implements the audit rules. .INPUTS System.String, System.String, System.DirectoryServices.ActiveDirectoryAuditRule[], System.Management.Automation.PSCredential .OUTPUTS None .NOTES AUTHOR: Michael Haken LASTEDIT: 2/27/2016 #> [CmdletBinding()] Param ( [Parameter(Position=0,ValueFromPipelineByPropertyName=$true,Mandatory=$true)] [string]$Domain, [Parameter(Position=1,ValueFromPipelineByPropertyName=$true,Mandatory=$true)] [AllowEmptyString()] [String]$ObjectCN, [Parameter(Position=2,ValueFromPipelineByPropertyName=$true,Mandatory=$true)] [System.DirectoryServices.ActiveDirectoryAuditRule[]]$Rules, [Parameter(Position=3,ValueFromPipelineByPropertyName=$true)] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credential = [System.Management.Automation.PSCredential]::Empty ) Begin { if ($Credential -eq $null) { $Credential = [System.Management.Automation.PSCredential]::Empty } $DN = (Get-ADDomain -Identity $Domain).DistinguishedName [String[]]$Drives = Get-PSDrive | Select-Object -ExpandProperty Name } Process { if ($DC -ne $null) { if (Test-Connection -ComputerName $DC) { $TempDrive = "tempdrive" if ($Drives.Contains($TempDrive)) { Write-Host "An existing PSDrive exists with name $TempDrive, temporarily removing" -ForegroundColor Yellow $OldDrive = Get-PSDrive -Name $TempDrive Remove-PSDrive -Name $TempDrive } $Drive = New-PSDrive -Name $TempDrive -Root "" -PSProvider ActiveDirectory -Server $Domain $CurrentLocation = Get-Location Set-Location -Path "$Drive`:\" if ($ObjectCN -eq "") { $ObjectDN = $DN } else { $ObjectDN = $ObjectCN + "," + $DN } $ObjectToChange = Get-ADObject -Identity $ObjectDN -Server $Domain $Path = $Drive.Name + ":" + $ObjectToChange.DistinguishedName try { $Acl = Get-Acl -Path $Path -Audit if ($Acl -ne $null) { foreach ($Rule in $Rules) { $Acl.AddAuditRule($Rule) } Set-Acl -Path $Path -AclObject $Acl Write-Results -Path $Path -Domain $Domain } else { Write-Warning "Could not retrieve the ACL for $Path" } } catch [System.Exception] { Write-Warning $_.ToString() } Remove-PSDrive $Drive if ($OldDrive -ne $null) { Write-Host "Recreating original PSDrive" -ForegroundColor Yellow New-PSDrive -Name $OldDrive.Name -PSProvider $OldDrive.Provider -Root $OldDrive.Root | Out-Null $OldDrive = $null } } else { Write-Host "Could not contact domain controller $DC" -ForegroundColor Red } } } End {} } Function New-InfrastructureObjectAuditRuleSet { <# .SYNOPSIS Creates the audit rule set for auditing the Infrastructure object. .DESCRIPTION The New-InfrastructureObjectAuditRuleSet cmdlet creates the required audit rule set for auditing the Infrastructure object. .EXAMPLE New-InfrastructureObjectAuditRuleSet Creates the audit rules. .INPUTS None .OUTPUTS [System.DirectoryServices.ActiveDirectoryAuditRule[]] .NOTES AUTHOR: Michael Haken LAST UPDATE: 2/27/2016 #> [CmdletBinding()] Param() Begin { $Everyone = New-Object System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::WorldSid, $null) } Process { $EveryoneFail = New-Object System.DirectoryServices.ActiveDirectoryAuditRule($Everyone, [System.DirectoryServices.ActiveDirectoryRights]::GenericAll, [System.Security.AccessControl.AuditFlags]::Failure, [System.DirectoryServices.ActiveDirectorySecurityInheritance]::None) #$objectguid = "cc17b1fb-33d9-11d2-97d4-00c04fd8d5cd" #Guid for change infrastructure master extended right if it was needed $EveryoneSuccess = New-Object System.DirectoryServices.ActiveDirectoryAuditRule($Everyone, @([System.DirectoryServices.ActiveDirectoryRights]::WriteProperty, [System.DirectoryServices.ActiveDirectoryRights]::ExtendedRight), [System.Security.AccessControl.AuditFlags]::Success, [System.DirectoryServices.ActiveDirectorySecurityInheritance]::None) [System.DirectoryServices.ActiveDirectoryAuditRule[]] $Rules = @($EveryoneFail, $EveryoneSuccess) } End { Write-Output $Rules } } Function New-DomainControllersAuditRuleSet { <# .SYNOPSIS Creates the audit rule set for auditing the domain controller's OU. .DESCRIPTION The New-DomainControllerAuditRuleSet cmdlet creates the required audit rule set for auditing the Domain Controller's OU. .EXAMPLE New-DomainControllersAuditRuleSet Creates the audit rules. .INPUTS None .OUTPUTS System.DirectoryServices.ActiveDirectoryAuditRule[] .NOTES AUTHOR: Michael Haken LAST UPDATE: 2/27/2016 #> [CmdletBinding()] Param() Begin { $Everyone = New-Object System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::WorldSid, $null) } Process { $EveryoneFail = New-Object System.DirectoryServices.ActiveDirectoryAuditRule($Everyone, [System.DirectoryServices.ActiveDirectoryRights]::GenericAll, [System.Security.AccessControl.AuditFlags]::Failure, [System.DirectoryServices.ActiveDirectorySecurityInheritance]::None) $EveryoneWriteDaclSuccess = New-Object System.DirectoryServices.ActiveDirectoryAuditRule($Everyone, [System.DirectoryServices.ActiveDirectoryRights]::WriteDacl, [System.Security.AccessControl.AuditFlags]::Success, [System.DirectoryServices.ActiveDirectorySecurityInheritance]::None) $EveryoneWritePropertySuccess = New-Object System.DirectoryServices.ActiveDirectoryAuditRule($Everyone, [System.DirectoryServices.ActiveDirectoryRights]::WriteProperty, [System.Security.AccessControl.AuditFlags]::Success, [System.DirectoryServices.ActiveDirectorySecurityInheritance]::All) [System.DirectoryServices.ActiveDirectoryAuditRule[]] $Rules = @($EveryoneFail, $EveryoneWriteDaclSuccess, $EveryoneWritePropertySuccess) } End { Write-Output $Rules } } Function New-EveryoneAuditRuleSet { <# .SYNOPSIS Creates the audit rule set for Everyone success and failures. .DESCRIPTION The New-EveryoneAuditRuleSet cmdlet creates the an audit rule set for success and failure on Everyone. .EXAMPLE New-EveryoneAuditRuleSet Creates the audit rules. .INPUTS None .OUTPUTS System.DirectoryServices.ActiveDirectoryAuditRule[] .NOTES AUTHOR: Michael Haken LASTEDIT: 2/27/2016 #> [CmdletBinding()] Param() Begin { $Everyone = New-Object System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::WorldSid, $null) } Process { $EveryoneFail = New-Object System.DirectoryServices.ActiveDirectoryAuditRule($Everyone, [System.DirectoryServices.ActiveDirectoryRights]::GenericAll, [System.Security.AccessControl.AuditFlags]::Failure, [System.DirectoryServices.ActiveDirectorySecurityInheritance]::None) $EveryoneSuccess = New-Object System.DirectoryServices.ActiveDirectoryAuditRule($Everyone, @([System.DirectoryServices.ActiveDirectoryRights]::WriteProperty, [System.DirectoryServices.ActiveDirectoryRights]::WriteDacl, [System.DirectoryServices.ActiveDirectoryRights]::WriteOwner), [System.Security.AccessControl.AuditFlags]::Success, [System.DirectoryServices.ActiveDirectorySecurityInheritance]::None) [System.DirectoryServices.ActiveDirectoryAuditRule[]] $Rules = @($EveryoneFail, $EveryoneSuccess) } End { Write-Output $Rules } } Function New-DomainAuditRuleSet { <# .SYNOPSIS Creates the audit rule set for domain object auditing. .DESCRIPTION The New-DomainAuditRuleSet cmdlet creates the audit rule set for the domain object. .PARAMETER DomainSID The domain SID object for the domain to associate teh rule set with. .EXAMPLE New-DomainAuditRuleSet Creates the audit rules. .INPUTS System.Security.Principal.SecurityIdentifier .OUTPUTS System.DirectoryServices.ActiveDirectoryAuditRule[] .NOTES AUTHOR: Michael Haken LASTEDIT: 2/27/2016 #> [CmdletBinding()] Param ( [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Mandatory=$true)] [System.Security.Principal.SecurityIdentifier]$DomainSID ) Begin { $Everyone = New-Object System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::WorldSid, $null) $DomainUsers = New-Object System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::AccountDomainUsersSid, $DomainSID) $Administrators = New-Object System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::BuiltinAdministratorsSid, $DomainSID) } Process { $EveryoneFail = New-Object System.DirectoryServices.ActiveDirectoryAuditRule($Everyone, [System.DirectoryServices.ActiveDirectoryRights]::GenericAll, [System.Security.AccessControl.AuditFlags]::Failure, [System.DirectoryServices.ActiveDirectorySecurityInheritance]::None) $DomainUsersSuccess = New-Object System.DirectoryServices.ActiveDirectoryAuditRule($DomainUsers, [System.DirectoryServices.ActiveDirectoryRights]::ExtendedRight, [System.Security.AccessControl.AuditFlags]::Success, [System.DirectoryServices.ActiveDirectorySecurityInheritance]::None) $AdministratorsSuccess = New-Object System.DirectoryServices.ActiveDirectoryAuditRule($Administrators, [System.DirectoryServices.ActiveDirectoryRights]::ExtendedRight, [System.Security.AccessControl.AuditFlags]::Success, [System.DirectoryServices.ActiveDirectorySecurityInheritance]::None) $EveryoneSuccess = New-Object System.DirectoryServices.ActiveDirectoryAuditRule($Everyone, @([System.DirectoryServices.ActiveDirectoryRights]::WriteProperty, [System.DirectoryServices.ActiveDirectoryRights]::WriteDacl, [System.DirectoryServices.ActiveDirectoryRights]::WriteOwner), [System.Security.AccessControl.AuditFlags]::Success, [System.DirectoryServices.ActiveDirectorySecurityInheritance]::None) [System.DirectoryServices.ActiveDirectoryAuditRule[]] $Rules = @($EveryoneFail, $DomainUsersSuccess, $AdministratorsSuccess, $EveryoneSuccess) } End { Write-Output $Rules } } Function New-PolicyContainerAuditRuleSet { <# .SYNOPSIS Creates the audit rule set for the Group Policy container. .DESCRIPTION The New-PolicyContainerAuditRuleSet cmdlet creates the required auditing rule set for group policy objects. .EXAMPLE New-PolicyContainerAuditRuleSet Creates the audit rules. .INPUTS None .OUTPUTS System.DirectoryServices.ActiveDirectoryAuditRule[] .NOTES AUTHOR: Michael Haken LAST UPDATED: 2/27/2016 #> [CmdletBinding()] Param() Begin { $Everyone = New-Object System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::WorldSid, $null) } Process { $EveryoneFail = New-Object System.DirectoryServices.ActiveDirectoryAuditRule($Everyone, [System.DirectoryServices.ActiveDirectoryRights]::GenericAll, [System.Security.AccessControl.AuditFlags]::Failure, [System.DirectoryServices.ActiveDirectorySecurityInheritance]::All) $EveryoneSuccess = New-Object System.DirectoryServices.ActiveDirectoryAuditRule($Everyone, @([System.DirectoryServices.ActiveDirectoryRights]::WriteProperty, [System.DirectoryServices.ActiveDirectoryRights]::WriteDacl), [System.Security.AccessControl.AuditFlags]::Success, [System.DirectoryServices.ActiveDirectorySecurityInheritance]::Descendents) [System.DirectoryServices.ActiveDirectoryAuditRule[]] $Rules = @($EveryoneFail, $EveryoneSuccess) } End { Write-Output $Rules } } Function New-RIDManagerAuditRuleSet { <# .SYNOPSIS Creates the audit rule set for the RID Manager object. .DESCRIPTION The New-RIDManagerAuditRuleSet cmdlet sets the required auditing for the RID Manager object. .EXAMPLE New-RIDManagerAuditRuleSet Creates the audit rules. .INPUTS None .OUTPUTS System.DirectoryServices.ActiveDirectoryAuditRule[] .NOTES AUTHOR: Michael Haken LAST UPDATED: 2/27/2016 #> [CmdletBinding()] Param() Begin { $Everyone = New-Object System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::WorldSid, $null) } Process { $EveryoneFail = New-Object System.DirectoryServices.ActiveDirectoryAuditRule($Everyone, [System.DirectoryServices.ActiveDirectoryRights]::GenericAll, [System.Security.AccessControl.AuditFlags]::Failure, [System.DirectoryServices.ActiveDirectorySecurityInheritance]::None) $EveryoneSuccess = New-Object System.DirectoryServices.ActiveDirectoryAuditRule($Everyone, @([System.DirectoryServices.ActiveDirectoryRights]::WriteProperty, [System.DirectoryServices.ActiveDirectoryRights]::ExtendedRight), [System.Security.AccessControl.AuditFlags]::Success, [System.DirectoryServices.ActiveDirectorySecurityInheritance]::None) [System.DirectoryServices.ActiveDirectoryAuditRule[]] $Rules = @($EveryoneFail, $EveryoneSuccess) } End { Write-Output $Rules } } Function Get-ForestDomains { <# .SYNOPSIS Gets all of the domains in the current Active Directory Forest. .DESCRIPTION The Get-ForestDomains cmdlet gets all of the domains in the current Active Directory Forest. .EXAMPLE Get-ForestDomains Gets all of the domains in the Forest of the logged on user. .INPUTS None .OUTPUTS System.String[] .NOTES AUTHOR: Michael Haken LAST UPDATED: 2/27/2016 #> [CmdletBinding()] Param () Begin {} Process { try { $Forest = Get-ADForest -Current LocalComputer $ForestDN = (Get-ADDomain -Identity ($Forest.RootDomain)).DistinguishedName } catch [System.Exception] { Write-Warning $_.Exception.Message Exit 1 } } End { Write-Output $Forest.Domains } } Function Write-Results { <# .SYNOPSIS Writes the ACL configuration output results. .DESCRIPTION The Write-Results cmdlet outputs the modified ACL. .PARAMETER Path The path of the Active Directory object to get the ACL of. .PARAMETER Domain The domain the object belongs to. .EXAMPLE Write-Results -Path "dc=contso,sc=com" -Domain "contoso.com" Writes the current ACL of the domain object. .INPUTS System.String, System.String .OUTPUTS Object .NOTES AUTHOR: Michael Haken LAST UPDATE: 2/27/2016 #> [CmdletBinding()] Param ( [Parameter(Position=0,ValueFromPipelineByPropertyName=$true,Mandatory=$true)] [string]$Path, [Parameter(Position=1,ValueFromPipelineByPropertyName=$true,Mandatory=$true)] [string]$Domain ) Begin {} Process { $Acl = Get-Acl -Path $Path Write-Host $Domain -ForegroundColor DarkRed -BackgroundColor White Write-Host ($Path.Substring($Path.IndexOf(":") + 1)) -ForegroundColor DarkRed -BackgroundColor White } End { Write-Output $Acl.Access } } Function Test-IsEnterpriseAdmin { <# .SYNOPSIS Tests if a user is a member of the Enterprise Admins group. .DESCRIPTION The Test-IsEnterpriseAdmin returns true if the user is in the group and false otherwise. .PARAMETER UserName The user to test the group membership on. If no user name is specified, the cmdlet runs against the current WindowsIdentity Principal. .PARAMETER Credential The credentials of the user to test enterprise admin rights. .EXAMPLE Test-IsEnterpriseAdmin Determines if the user credentials being used to run the cmdlet have Enterprise Admin privileges .EXAMPLE Test-IsEnterpriseAdmin -UserName "John Smith" Determines if the user John Smith has Enterprise Admin privileges .INPUTS System.String System.Management.Automation.PSCredential .OUTPUTS System.Boolean .NOTES AUTHOR: Michael Haken LASTEDIT: 2/27/2017 #> [CmdletBinding()] Param ( [Parameter(Position=0,ValueFromPipeLine=$true,ValueFromPipeLineByPropertyName=$true,ParameterSetName="Username")] [string]$UserName = [System.String]::Empty, [Parameter(Position=0,ValueFromPipeLine=$true,ValueFromPipeLineByPropertyName=$true,ParameterSetName="Credential")] [PSCredential]$Credential = [PSCredential]::Empty ) Begin { try{ Import-Module ActiveDirectory } catch [Exception] { Write-Warning "This cmdlet requires the Active Directory module." Exit 1 } [bool]$IsAdmin = $false if ($Credential -eq $null) { $Credential = [System.Management.Automation.PSCredential]::Empty } } Process { switch ($PSCmdlet.ParameterSetName) { "Username" { if ($UserName -ne [System.String]::Empty) { $CurrentUser = $UserName.Substring($UserName.IndexOf("\") + 1) } else { $Principal = [System.Security.Principal.WindowsIdentity]::GetCurrent() if ($Principal.IsSystem) { Write-Host "Current principal is the SYSTEM account." $Role = Get-WmiObject -Class Win32_OperatingSystem -Property ProductType | Select-Object -ExpandProperty ProductType if ($Role -eq 2) { Write-Host "Current principal is a domain controller." $IsAdmin = $true } else { Write-Warning "Current principal is the SYSTEM account, but not a domain controller." $CurrentUser = $Principal.Name.Substring($Principal.Name.IndexOf("\") + 1) } } else { $CurrentUser = $Principal.Name.Substring($Principal.Name.IndexOf("\") + 1) } } } "Credential" { if ($Credential -ne [PSCredential]::Empty) { $CurrentUser = $Credential.UserName.Substring($Credential.UserName.IndexOf("\") + 1) } else { $Principal = [System.Security.Principal.WindowsIdentity]::GetCurrent() Write-Host "Current principal is $($Principal.Name) and is system: $($Principal.IsSystem)" if ($Principal.IsSystem) { Write-Host "Current principal is the SYSTEM account." $Role = Get-WmiObject -Class Win32_OperatingSystem -Property ProductType | Select-Object -ExpandProperty ProductType if ($Role -eq 2) { Write-Host "Current principal is a domain controller." $IsAdmin = $true } else { Write-Warning "Current principal is the SYSTEM account, but not a domain controller." $CurrentUser = $Principal.Name.Substring($Principal.Name.IndexOf("\") + 1) } } else { $CurrentUser = $Principal.Name.Substring($Principal.Name.IndexOf("\") + 1) } } } default { throw "Could not determine parameter set name for Test-IsEnterpriseAdmin." } } if(!$IsAdmin) { Write-Host "Getting groups for $CurrentUser" $Groups = Get-NestedGroupMembership -Principal $CurrentUser | Select-Object -Property Name,SID $RootDomainSID = Get-ADDomain -Identity (Get-ADForest -Current LoggedOnUser).RootDomain | Select-Object -ExpandProperty DomainSID [Security.Principal.SecurityIdentifier]$EnterpriseAdminSID = New-Object System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::AccountEnterpriseAdminsSid, $RootDomainSID) foreach ($Group in $Groups) { if ($Group.SID -eq $EnterpriseAdminSID) { $IsAdmin = $true break } } } } End { Write-Output $IsAdmin } } Function Get-NestedGroupMembership { <# .SYNOPSIS Recursively gets the group membership of an AD principal. .DESCRIPTION The Get-NestedGroupMembership gets all nested group membership of an AD principal. .PARAMETER Principal The principal to get group membership for. .PARAMETER Groups This parameter does not need to be specified, it is used by the recursion algorithm .EXAMPLE Get-NestedGroupMembership -Principal Administrator Gets all group membership for the Administrator account. .INPUTS System.String, Microsoft.ActiveDirectory.Management.ADGroup[] .OUTPUTS Microsoft.ActiveDirectory.Management.ADGroup[] .NOTES AUTHOR: Michael Haken LAST UPDATE: 2/27/2016 #> [CmdletBinding()] Param( [Parameter(ValueFromPipelineByPropertyName=$true,ValueFromPipeline=$true,Position=0)] [string]$Principal, [Parameter(ValueFromPipelineByPropertyName=$true,Position=1)] [Microsoft.ActiveDirectory.Management.ADGroup[]]$Groups ) Begin { try{ Import-Module ActiveDirectory } catch [Exception] { Write-Warning "This cmdlet requires the Active Directory module." Exit 1 } if ($Groups -eq $null) { $Groups = @() } if ([System.String]::IsNullOrEmpty($Principal)) { $Principal = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name $Principal = $Principal.Substring($Principal.IndexOf("\") + 1) } if ($Principal.IndexOf("DC=") -ne -1) { $Server = $Principal.Substring($Principal.IndexOf("DC=")).Replace("DC=","").Replace(",",".") } else { $Server = (Get-ADDomain -Current LoggedOnUser).DnsRoot } } Process { #Get the group membership of the evaluated principal $TempGroups = Get-ADPrincipalGroupMembership -Identity $Principal -Server $Server $GroupsToCheck = @() #Iterate through these groups, need to check if the Groups array already contains the group #If it doesn't, add the group since it is a newly discovered nested group, and also add it to the #array of groups to check for further nested group membership #We don't want to check the Groups array for nested membership since a lot of those groups have already been checked foreach($Group in $TempGroups) { if (!$Groups.Contains($Group)) { $Groups += $Group $GroupsToCheck += $Group } } #This array will hold newly discovered nested groups of the groups we need to check $NewGroups = @() #Get the nested group membership of each new group to check foreach ($Group in $GroupsToCheck) { $NewGroups += Get-NestedGroupMembership -Principal $Group.DistinguishedName -Groups $Groups } #After getting the nested groups, check to see if that group may have been added already to Groups #through nested membership in some other group that was already checked foreach ($Group in $NewGroups) { if (!$Groups.Contains($Group)) { $Groups += $Group } } } End { #Return the updated total group membership Write-Output $Groups } } |