Private/Convert-SidToName.ps1
Function Convert-SidToName { <# .SYNOPSIS Converts a Security Identifier (SID) to its corresponding NT Account Name with improved error handling. .DESCRIPTION This function translates a given Security Identifier (SID) to the corresponding NT Account Name using .NET Framework classes. It accepts both string representations of SIDs and SID objects. The function first checks against a comprehensive list of Well-Known SIDs before attempting dynamic resolution through the Windows API. It implements caching to optimize performance in large Active Directory environments where the same SIDs may be repeatedly resolved. For improved security and reliability, the function performs thorough validation of input SIDs before processing and handles various error conditions that might occur during translation. This version has been enhanced with improved error handling to prevent terminating errors when dealing with invalid SIDs, which is particularly important in GPO processing contexts. The function supports pipeline input, making it suitable for batch processing of SIDs. .PARAMETER SID The Security Identifier (SID) to convert. This parameter accepts: - String representation of a SID (e.g., "S-1-5-32-544") - System.Security.Principal.SecurityIdentifier object - Any object with a SID property containing either of the above This parameter supports pipeline input by value and by property name. .EXAMPLE Convert-SidToName -SID 'S-1-5-21-3623811015-3361044348-30300820-1013' # Output: EguibarIT\davade Converts the specified domain SID string to its corresponding NT Account Name. .EXAMPLE Get-ADUser -Identity davade | Select-Object SID | Convert-SidToName Retrieves the SID for user davade from Active Directory and converts it to the corresponding NT Account Name. .EXAMPLE "S-1-5-32-544" | Convert-SidToName # Output: BUILTIN\Administrators Converts the Well-Known SID for the Administrators group using pipeline input. .INPUTS System.String, System.Security.Principal.SecurityIdentifier, Microsoft.ActiveDirectory.Management.ADObject .OUTPUTS System.String .NOTES Used Functions: Name ║ Module/Namespace ═════════════════════════════════════════════╬══════════════════════════════ Write-Verbose ║ Microsoft.PowerShell.Utility Write-Warning ║ Microsoft.PowerShell.Utility Write-Debug ║ Microsoft.PowerShell.Utility Write-Error ║ Microsoft.PowerShell.Utility Get-Date ║ Microsoft.PowerShell.Utility Set-StrictMode ║ Microsoft.PowerShell.Utility Test-Path ║ Microsoft.PowerShell.Management Get-FunctionDisplay ║ EguibarIT.DelegationPS Test-IsValidSID ║ EguibarIT.DelegationPS Get-AdWellKnownSID ║ EguibarIT.DelegationPS .NOTES Version: 1.3 DateModified: 27/May/2025 LastModifiedBy: Vicente Rodriguez Eguibar vicente@eguibar.com Eguibar IT http://www.eguibarit.com .LINK https://github.com/vreguibar/EguibarIT.DelegationPS/blob/main/Private/Convert-SidToName.ps1 .COMPONENT Active Directory .ROLE Security .FUNCTIONALITY SID to Name Conversion, Directory Lookup #> [CmdletBinding()] [OutputType([string])] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0)] [ValidateNotNullOrEmpty()] [alias('ID')] [object] $SID ) Begin { Set-StrictMode -Version Latest # Display function header if variables exist if ($null -ne $Variables -and $null -ne $Variables.HeaderDelegation) { $txt = ($Variables.HeaderDelegation -f (Get-Date).ToString('dd/MMM/yyyy'), $MyInvocation.Mycommand, (Get-FunctionDisplay -HashTable $PsBoundParameters -Verbose:$False) ) Write-Verbose -Message $txt } #end If ############################## # Variables Definition # Static hashtable to cache SID → Name mappings if (-not (Test-Path -Path variable:script:SidNameCache)) { $script:SidNameCache = @{ } } #end If # Regex pattern for validating SID format $sidRegex = '^S-\d+-\d+(-\d+)*$' # Regex pattern for privilege rights keys $privilegeKeyRegex = '^Se[A-Za-z]+Privilege$|^Se[A-Za-z]+Right$' } #end Begin Process { $sidValue = $null $result = $null # Skip processing if input matches privilege key pattern if ($SID -is [string] -and $SID -match $privilegeKeyRegex) { Write-Debug -Message ('Value appears to be a privilege key: {0}, skipping resolution' -f $SID) return $null } #end if # Extract SID string from input try { if ($SID -is [System.Security.Principal.SecurityIdentifier]) { $sidValue = $SID.Value } elseif ($SID -is [string]) { $sidValue = $SID } elseif ($null -ne $SID.ObjectSID) { if ($SID.ObjectSID -is [System.Security.Principal.SecurityIdentifier]) { $sidValue = $SID.ObjectSID.Value } else { $sidValue = $SID.ObjectSID.ToString() } #end if-elseif } else { # Try to convert to string and check if it's a valid SID pattern $sidString = $SID.ToString() if ($sidString -match $sidRegex) { $sidValue = $sidString } else { Write-Warning -Message ('Cannot extract SID from input object: {0}' -f $SID) return $null } #end if-else } #end if-elseif-else } catch { Write-Error -Message ('Error extracting SID value: {0}' -f $_.Exception.Message) return $null } #end try-catch # If no SID extracted, return null if ([string]::IsNullOrEmpty($sidValue)) { Write-Error -Message 'Extracted SID value is null or empty.' return $null } #end if # Validate SID format if (-not ($sidValue -match $sidRegex)) { Write-Error -Message ('Invalid SID format: {0}' -f $sidValue) return $null } #end if # Check if result is cached if ($script:SidNameCache.ContainsKey($sidValue)) { Write-Debug -Message ('Using cached value for SID {0}: {1}' -f $sidValue, $script:SidNameCache[$sidValue]) return $script:SidNameCache[$sidValue] } #end if # Check if the SID is a Well-Known SID # ToDo: Consider if this block should be at the beginning of the Process block. Early detect a WellKnown sid is good. try { $wellKnownSid = Get-AdWellKnownSID -SID $sidValue -ErrorAction SilentlyContinue if ($null -ne $wellKnownSid) { $script:SidNameCache[$sidValue] = $wellKnownSid return $wellKnownSid } #end if } catch { Write-Debug -Message ('Error checking Well-Known SIDs: {0}' -f $_.Exception.Message) # Continue with other resolution methods } #end try-catch # Try to resolve SID to name using .NET try { $sidObj = [System.Security.Principal.SecurityIdentifier]::new($sidValue) $ntAccount = $sidObj.Translate([System.Security.Principal.NTAccount]) if ($null -ne $ntAccount) { $result = $ntAccount.Value $script:SidNameCache[$sidValue] = $result return $result } #end if } catch [System.Security.Principal.IdentityNotMappedException] { Write-Error -Message ('SID {0} cannot be resolved to a name (not found)' -f $sidValue) return $null } catch { Write-Error -Message ('Error translating SID {0}: {1}' -f $sidValue, $_.Exception.Message) return $null } #end try-catch # If all methods fail, return null return $null } #end Process End { if ($null -ne $Variables -and $null -ne $Variables.FooterDelegation) { $txt = ($Variables.FooterDelegation -f $MyInvocation.InvocationName, 'Converted SID to Name.') Write-Verbose -Message $txt } #end if } #end End } #end Function Convert-SidToName |