Let’s drill down to the methods we can use within Powershell to retrieve Active Directory permissions and translate the GUID:
- How can we get the name of a property or (extended) right in the Active Directory that a specific user has on a specific OU (path)?
- How can we do this without having to install anything on our server, like Active Directory Remote Management Powershell Extensions?
- How can we do this without using .NET code and without features/extensions?
How can we get the name of a property or (extended) right in the Active Directory that a specific user has on a specific OU (path)?
The first question could be answered with the advise to use the QAD Powershell cmdlets, specifically the cmdlet Get-QADPermission
. An alternative was to use Get-ACL
on Active Directory objects, but this needs the ActiveDirectory Powershell Module (Add Feature: “Active Directory module for Windows PowerShell” at “Remote Server Administration Tools” > “Role Administration Tools” > “AD DS and AD LDS Tools”).
How can we do this without having to install anything on our server, like Active Directory Remote Management Powershell Extensions?
The second question lead me the way to this book: The .NET Developer’s Guide to Directory Services Programming, through this link http://flylib.com/books/en/1.434.1.83/1/. The code is found at “Listing 8.4. GUID-to-Friendly-Name Conversion” and the BuildFilterOctetString from “Listing 4.2. Converting Binary to String for Search Filters”.
We could include .NET code in our Powershell script/function/module, this requires to add the .NET code from the book using the here-string method and use the functions in that .NET code in our Powershell script.
I implemented this using the code from the book and a reference to the DirectoryServices assembly:
1 2 3 4 5 6 7 8 |
$SchemaGuidConversion = @" all .NET code from the book goes here "@ Add-Type $SchemaGuidConversion -ReferencedAssemblies System.DirectoryServices $guid = "00299570-246d-11d0-a768-00aa006e0529" $ExtendedRightsDN = "LDAP://cn=Extended-Rights,cn=configuration,DC=your,DC=domain" [SchemaGuidConversion]::GetNameForRightsGuid($guid,$ExtendedRightsDN) |
The key functions are:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
public class SchemaGuidConversion { public static string GetNameForRightsGuid(Guid rightsGuid, DirectoryEntry extendedRightsRoot ) { string filter = String.Format( "(rightsGuid={0})", rightsGuid.ToString("D") ); return GetNameForGuid( filter, "cn", extendedRightsRoot ); } public static string GetNameForSchemaGuid( Guid schemaIDGuid, DirectoryEntry schemaRoot ) { //string filter = String.Format( "(schemaIDGUID={0})", BuildFilterOctetString( schemaIDGuid.ToByteArray() ) ); string filter = String.Format( "(schemaIDGUID={0})", BuildFilterOctetString( schemaIDGuid.ToString() ) ); return GetNameForGuid( filter, "ldapDisplayName", schemaRoot ); } public static string GetNameForGuid( string filter, string targetAttribute, DirectoryEntry searchRoot ) { string attributeName = null; SearchResult result; DirectorySearcher searcher = new DirectorySearcher(searchRoot); searcher.SearchScope = SearchScope.OneLevel; searcher.PropertiesToLoad.Add(targetAttribute); searcher.Filter = filter; using (searcher) { result = searcher.FindOne(); if (result != null) { attributeName = (string) result.Properties[targetAttribute][0]; } } return attributeName; } public static string BuildFilterOctetString(string objectGuid) { System.Guid guid = new Guid(objectGuid); byte[] byteGuid = guid.ToByteArray(); string queryGuid = ""; foreach (byte b in byteGuid) { queryGuid += @"\" + b.ToString("x2"); } return queryGuid; } } |
How can we do this without using .NET code and without features/extensions?
This meant I had to find or build the right Powershell code to do the same as the .NET code above. It seemed difficult but the key was to follow the .NET code and replicate it in Powershell.
The basics are:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
$guid = "the-guid-string-from-an-accessrule" $DomainDC = ([ADSI]"").distinguishedName $ExtendedRightGUIDs = "LDAP://cn=Extended-Rights,cn=configuration,$DomainDC" $PropertyGUIDs = "LDAP://cn=schema,cn=configuration,$DomainDC" $rightsGuid = $guid $property = "cn" $SearchAdsi = ([ADSISEARCHER]"(rightsGuid=$rightsGuid)") $SearchAdsi.SearchRoot = $ExtendedRightGUIDs $SearchAdsi.SearchScope = "OneLevel" $SearchAdsiRes = $SearchAdsi.FindOne() If($SearchAdsiRes){ Return $SearchAdsiRes.Properties[$property] }Else{ $SchemaGuid = $guid $SchemaByteString = "\" + ((([guid]$SchemaGuid).ToByteArray() | %{$_.ToString("x2")}) -Join "\") $property = "ldapDisplayName" $SearchAdsi = ([ADSISEARCHER]"(schemaIDGUID=$SchemaByteString)") $SearchAdsi.SearchRoot = $PropertyGUIDs $SearchAdsi.SearchScope = "OneLevel" $SearchAdsiRes = $SearchAdsi.FindOne() If($SearchAdsiRes){ Return $SearchAdsiRes.Properties[$property] }Else{ Write-Host -f Yellow $guid Return $guid.ToString() } } |
Our context for which we needed this:
In our customer environment we are installing SharePoint which relies heavily on several OS configurations and permissions. We have built an entire Powershell script for the automated installation and configuration of the whole A to Z process including OS firewall and updating AppFabric. The installation script needs to be able to run smoothly and the end result needs to be the same for every environment (all-in-one development image on laptop, 3 server set-up, 8 server set-up, etc.). We do not want to install anything (OS Roles/Features, third party tools) on our servers that is not used by our application (SharePoint/SQL/OfficeWebApps/ADFS/etc).
Since the administration of OS, AD, policies, rights, etc is done by others (except for our laptop) and mistakes happen, we script checks to test all conditions, permissions, settings and versions.
One of these checks is to test the required rights in the Active Directory. We have a password reset and password change webpart, which require specific Active Directory permissions, like “User-Force-Change-Password”.
We can acquire an access rule collection for the LDAP Path to the User Accounts OU in this way:
(New-Object System.DirectoryServices.DirectoryEntry(“LDAP://$UserPath”)).PSBase.ObjectSecurity.Access
This will return an array of ActiveDirectoryAccessRule objects, like this:
ActiveDirectoryRights : CreateChild, Self, WriteProperty, ExtendedRight, Delete, GenericRead, WriteDacl, WriteOwner
InheritanceType : All
ObjectType : 00000000-0000-0000-0000-000000000000
InheritedObjectType : 00000000-0000-0000-0000-000000000000
ObjectFlags : None
AccessControlType : Allow
IdentityReference : BUILTIN\Administrators
IsInherited : True
InheritanceFlags : ContainerInherit
PropagationFlags : None
Above corresponds to “All” objects and properties. In case of specific permissions, the ObjectType and InheritedObjectType values are GUID’s for the property, object or special permisson.
In our case, we could expect something like this:
ActiveDirectoryRights : ExtendedRight
InheritanceType : Descendents
ObjectType : 00299570-246d-11d0-a768-00aa006e0529
InheritedObjectType : bf967aba-0de6-11d0-a285-00aa003049e2
ObjectFlags : ObjectAceTypePresent, InheritedObjectAceTypePresent
AccessControlType : Allow
IdentityReference : DOMAIN\AccountManagement
IsInherited : False
InheritanceFlags : ContainerInherit
PropagationFlags : InheritOnly
Somehow we want to translate these GUID’s to something useful and understandable.
The functions we created with our Powershell code to find and translate GUID’s to rights:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
function Get-NameForGUID{ [CmdletBinding()] Param( [guid]$guid ) Begin{ $DomainDC = ([ADSI]"").distinguishedName $ExtendedRightGUIDs = "LDAP://cn=Extended-Rights,cn=configuration,$DomainDC" $PropertyGUIDs = "LDAP://cn=schema,cn=configuration,$DomainDC" } Process{ If($guid -eq "00000000-0000-0000-0000-000000000000"){ Return "All" }Else{ $rightsGuid = $guid $property = "cn" $SearchAdsi = ([ADSISEARCHER]"(rightsGuid=$rightsGuid)") $SearchAdsi.SearchRoot = $ExtendedRightGUIDs $SearchAdsi.SearchScope = "OneLevel" $SearchAdsiRes = $SearchAdsi.FindOne() If($SearchAdsiRes){ Return $SearchAdsiRes.Properties[$property] }Else{ $SchemaGuid = $guid $SchemaByteString = "\" + ((([guid]$SchemaGuid).ToByteArray() | %{$_.ToString("x2")}) -Join "\") $property = "ldapDisplayName" $SearchAdsi = ([ADSISEARCHER]"(schemaIDGUID=$SchemaByteString)") $SearchAdsi.SearchRoot = $PropertyGUIDs $SearchAdsi.SearchScope = "OneLevel" $SearchAdsiRes = $SearchAdsi.FindOne() If($SearchAdsiRes){ Return $SearchAdsiRes.Properties[$property] }Else{ Write-Host -f Yellow $guid Return $guid.ToString() } } } } } function Get-RightsInAD{ [CmdletBinding()] Param( [string]$UserName, [string]$DomainDC = ([ADSI]"").distinguishedName, [string]$UserPath = "OU=Users,OU=Accounts,$DomainDC" ) #Set Verbose and Debug params if (-not $PSBoundParameters.ContainsKey('Verbose')) { $VerbosePreference = $PSCmdlet.GetVariableValue('VerbosePreference') }else{ $VerbosePreference = [System.Management.Automation.ActionPreference]::Continue } if (-not $PSBoundParameters.ContainsKey('Debug')) { $DebugPreference = $PSCmdlet.GetVariableValue('DebugPreference') }else{ $DebugPreference = [System.Management.Automation.ActionPreference]::Continue } If( ![System.String]::IsNullOrEmpty($UserName) ){ $iBackSlash = $UserName.IndexOf("\") if(($iBackSlash -ne "-1")) { $UserName = ($UserName.Split('\'))[1] } $AccountEntry = ([ADSISEARCHER]"samaccountname=$UserName").Findone() If($AccountEntry.Path){ $DistinguishedNamePath = $AccountEntry.Properties["distinguishedName"] }Else{ Throw "FAILED TO RETRIEVE USER FROM AD BY SAM ACCOUNTNAME $UserName" } $Groups = @(Get-UserGroupsInAD $DistinguishedNamePath) $Groups += $UserName } $UserAccountsPath = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$UserPath") if(!$UserAccountsPath.Path){ Throw "FAILED TO RETRIEVE USER ACCOUNT OU FROM AD BY PATH $UserPath" } $AccessRuleCollection = $Entry.PSBase.ObjectSecurity.Access | ?{$_.AccessControlType -eq [System.Security.AccessControl.AccessControlType]::Allow} | ?{$_.IdentityReference -is [System.Security.Principal.NTAccount]} If( ![System.String]::IsNullOrEmpty($UserName) ){ $AccessRuleCollection = $AccessRuleCollection | ?{$Groups -Contains $_.IdentityReference.ToString().Split("\")[-1]} } $AccessRights = @() $AccessRuleCollection | %{ If($_.ActiveDirectoryRights -eq "GenericAll"){ $AccessPermissions = "All" }ElseIf($_.ActiveDirectoryRights -eq "ExtendedRight" -or $_.ActiveDirectoryRights -Match "ExtendedRight"){ $AccessPermissions = "Special" }Else{ $AccessPermissions = $_.ActiveDirectoryRights.ToString() } $AccessProperties = Get-NameForGUID $_.ObjectType $AccessObject = Get-NameForGUID $_.InheritedObjectType $AccessRights += @{ Permissions = $AccessPermissions; Properties = $AccessProperties; Object = $AccessObject; ActiveDirectoryRights = $_.ActiveDirectoryRights; IdentityReference = $_.IdentityReference } } Return $AccessRights } |