Use PowerShell to Translate a User’s SID to an Active Directory Account Name

Use PowerShell to Translate a User’s SID to an Active Directory Account Name

  • Comments 9
  • Likes

 

Summary: Microsoft Scripting Guy Ed Wilson shows how to use Windows PowerShell to translate a user's SID to an Active Directory Domain Services account name.

 

Hey, Scripting Guy! QuestionHey, Scripting Guy! It seems that whenever I search for Windows PowerShell scripts to translate a user name into a SID, all I can find is a script that uses WMI. WMI is too slow for our network. Is there a better way to do this?

-- KW

 

Hey, Scripting Guy! Answer Hello KW,

Microsoft Scripting Guy Ed Wilson here. You can easily use the .NET Framework classes in a Windows PowerShell script to translate a user name to a security identifier (SID). In addition, you can use a .NET Framework class to translate a SID to a user name, or you can simply take the SID and use LDAP to retrieve the user name. I will talk about all these techniques in today’s article. I created the UserToSid-SidToUser.ps1 script to illustrate these techniques. The complete script is shown here.

UserToSid-SidToUser.ps1

<#
   .Synopsis
    Translates a user name to a SID or a SID to a user name.
   .Description
    This script translates a user name to a SID or a SID to a user name.
    Note: To translate the user name to the SID, you must
    use the logon name (SAMAccountName), and not the full user name.
   .Example
    UserToSid.ps1  -user "mytestuser"
    Displays SID of mytestuser in current domain
   .Example
    UserToSid.ps1  -sid "S-1-5-21-1877799863-120120469-1066862428-500"
    Displays user with SID of "S-1-5-21-1877799863-120120469-1066862428-500"
   .Inputs
    [string]
   .OutPuts
    [string]
   .Notes
    NAME:  UserToSid-SidToUser.ps1
    AUTHOR: Ed Wilson
    LASTEDIT: 10/05/2010
    VERSION: 2.0
    KEYWORDS: Active Directory, user accounts, Security.Principal.SecurityIdentifier
   .Link
     Http://www.ScriptingGuys.com
#Requires -Version 2.0
#>
param(
      [string]
      $domain = $env:USERDOMAIN,
      [string]
      $user,
      [string]
      $sid
      ) #end param

# Begin Functions

function New-Underline
{
<#
.Synopsis
 Creates an underline the length of the input string
.Example
 New-Underline -strIN "Hello world"
.Example
 New-Underline -strIn "Morgen welt" -char "-" -sColor "blue" -uColor "yellow"
.Example
 "this is a string" | New-Underline
.Notes
 NAME:
 AUTHOR: Ed Wilson
 LASTEDIT: 5/20/2009
 VERSION: 2.0
 KEYWORDS: scripting techniques, string manipulation
.Link
 Http://www.ScriptingGuys.com
#>
[CmdletBinding()]
param(
      [Parameter(Mandatory = $true,Position = 0,valueFromPipeline=$true)]
      [string]
      $strIN,
      [string]
      $char = "=",
      [string]
      $sColor = "Green",
      [string]
      $uColor = "darkGreen",
      [switch]
      $pipe
 ) #end param
 $strLine= $char * $strIn.length
 if(-not $pipe)
  {
   Write-Host -ForegroundColor $sColor $strIN
   Write-Host -ForegroundColor $uColor $strLine
  }
  Else
  {
   $strIn
   $strLine
  }
} #end New-Underline function

Function Get-UserToSid()
{
  $ntAccount = new-object System.Security.Principal.NTAccount($domain, $user)
  $sid = $ntAccount.Translate([System.Security.Principal.SecurityIdentifier])
  New-UnderLine("$domain/$user sid is:")
  ($local:sid).value
  exit
} #end UserToSid

Function Get-SidToUser()
{
 New-Underline("Obtaining SID translation ... this might take a bit of time ...")
 New-UnderLine("Sid: $sid is:")
 [adsi]"LDAP://<SID=$sid>"
 exit
} #end sidToUser

# *** Entry point to script ***

if($sid)       { Get-SidToUser }
if($user)      { Get-UserToSid }

The UserToSid-SidToUser.ps1 script begins with a comment block. This block uses comment-based help to provide two things to our script. It contains the normal header we would put in a comment block at the top of our script. This includes the author of the script, the name of the script, when the script was written, notes about any special features, what the script uses, and special requirements. These are the sort of things you would want to include for any script you wrote, whether the language is batch, VBScript, Perl, Rexx, Jscript, or Windows PowerShell. In Windows PowerShell 1.0, if you wanted to make a multiline comment, you had to place a number character (#) at the beginning of each line. I still do this when I want my comments to stand out. A better way to do this, however, in Windows PowerShell 2.0 is to use the multiline comment characters.

I talked about adding help to a Windows PowerShell script the week of January 4, 2010:

I added the comment block at the beginning of this script by using the technique outlined in the Weekend Scripter article, Automatically Add Comment-Based Help to Your PowerShell Scripts.

The header portion of our script is, therefore, contained in a multiline comment block. This comment block is shown here:

<#

   .Synopsis

    Translates a user name to a SID or a SID to a user name.

   .Description

    This script translates a user name to a SID or a SID to a user name.

    Note: To translate the user name to the SID, you must

    use the logon name (SAMAccountName), and not the full user name.

   .Example

    UserToSid.ps1  -user "mytestuser"

    Displays SID of mytestuser in current domain

   .Example

    UserToSid.ps1  -sid "S-1-5-21-1877799863-120120469-1066862428-500"

    Displays user with SID of "S-1-5-21-1877799863-120120469-1066862428-500"

   .Inputs

    [string]

   .OutPuts

    [string]

   .Notes

    NAME:  UserToSid-SidToUser.ps1

    AUTHOR: Ed Wilson

    LASTEDIT: 10/05/2010

    VERSION: 2.0

    KEYWORDS: Active Directory, user accounts, Security.Principal.SecurityIdentifier

   .Link

     Http://www.ScriptingGuys.com

#Requires -Version 2.0

#>

Because I followed the rules for adding comment-based help, the header serves two purposes: It documents the script, and allows me to get help directly from the command line. This means I do not have to open the script in the Windows PowerShell ISE or some other script editor just to see what the script does. This is shown in the following image.

Image of getting help directly from the command line

The cool thing is that when using comment-based help, it completely integrates with the Windows PowerShell help system via the Get-Help cmdlet. This means that if I only want to see a sample of the syntax, I use Get-Help c:\fso\UserToSid-SidToUser.ps1 –Examples. This is shown in the following image.

Image of getting sample syntax

Please do not think that a command such as Get-Help c:\fso\UserToSid-SidToUser.ps1 –Examples is too much typing. I use tab completion to type those type of things. Here is what I actually typed (keep in mind that <tab> is pressing the tab key, not actually typing left angle bracket t a b and right angle bracket).

get-h <tab> c:\f <tab> usert <tab> -e <tab>

When typing a command from the Windows PowerShell command line, all you have to type is enough of the command to distinguish it from other commands. If you are not sure how much that is, and you type a command and press Tab, and if the command that appears is not what you want, press Tab again. It means that there were several commands that would match. For example, if you were to type only the letter g and press Tab, eventually you would get to the command you want to run. The advantage of this is that most Windows PowerShell commands are readable. If you find yourself typing a command over and over, and the use of Tab completion is still too much for you, you can create an alias to the command. The cool thing is that you can also create functions, and then create an alias for your functions.

By creating custom functions and custom aliases, and perhaps storing your aliases in your Windows PowerShell profile, you can make Windows PowerShell work the way you want it to. I have written more than a dozen Hey, Scripting Guy! Blog posts that talk about working with the Windows PowerShell profile. You can use this search string to get started reviewing my articles about using the Windows PowerShell profile. In How Can I Create a Custom Function?, I talk about modifying the behavior of an existing Windows PowerShell cmdlet and then creating a custom alias for it.

The script creates three parameters. One parameter, $domain, pulls the user’s domain information from the environment variables. If you wish to query a different domain, you will need to supply a different value when calling the script. The other two parameters, $sid and $user, take effect only when they are present:

param(

      [string]

      $domain = $env:USERDOMAIN,

      [string]

      $user,

      [string]

      $sid

      ) #end param

This script does not accept an array of users for input, and it will generate the error seen here if you attempt to supply an array of users to it:

PS C:\> $a = "ed","teresa"

PS C:\> C:\fso\UserToSid-SidToUser.ps1 -user $a

Exception calling "Translate" with "1" argument(s): "Some or all identity references

 could not be translated."

At C:\fso\UserToSid-SidToUser.ps1:89 char:30

+   $sid = $ntAccount.Translate <<<< ([System.Security.Principal.SecurityIdentifier]

)

    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException

    + FullyQualifiedErrorId : DotNetMethodException

On the other hand, if you use the pipeline and the ForEach-Object cmdlet (with the alias, %), you can easily work around this limitation. This is illustrated here:

PS C:\> $a = "ed","teresa"

PS C:\> $a | % { C:\fso\UserToSid-SidToUser.ps1 -user $_ }

NWTRADERS/ed sid is:

====================

S-1-5-21-3746122405-834892460-3960030898-1115

NWTRADERS/teresa sid is:

========================

S-1-5-21-3746122405-834892460-3960030898-1207

PS C:\>

Because you can supply an array of user names via the pipeline, it means that the Get-Content cmdlet can be used to read a text file, such as the users.txt file shown in the following image.

Image of users.txt file

PS C:\> $a = Get-Content C:\fso\users.txt

PS C:\> $a | % { C:\fso\UserToSid-SidToUser.ps1 -user $_ }

NWTRADERS/ed SID is:

====================

S-1-5-21-3746122405-834892460-3960030898-1115

NWTRADERS/teresa SID is:

========================

S-1-5-21-3746122405-834892460-3960030898-1207

NWTRADERS/bob SID is:

=====================

S-1-5-21-3746122405-834892460-3960030898-3601

NWTRADERS/administrator SID is:

===============================

S-1-5-21-3746122405-834892460-3960030898-500

PS C:\>

I use the System.Security.Principal.NTAccount .NET Framework class to perform the translation to SID. The NTAccount class resides in the System.Security.Principal .NET Framework namespace. When calling the translate method from the NTAccount class,I tell it that I want to translate the account name to a security identifier by specifying the System.Security.Principal.SecurityIdentifier class as type to translate. This is shown here:

Function Get-UserToSid()

{

  $ntAccount = new-object System.Security.Principal.NTAccount($domain, $user)

  $sid = $ntAccount.Translate([System.Security.Principal.SecurityIdentifier])

  New-UnderLine("$domain/$user sid is:")

  ($local:sid).value

  exit

} #end UserToSid

I could use the SecurityIdentifier .NET Framework class to translate from SID to user name. If I did, the code would look something like the following:

Function Sid-toUser

{

 $sidString = "S-1-5-21-3746122405-834892460-3960030898-500"

 $sid = new-object System.Security.Principal.SecurityIdentifier($sidString)

 $user = $sid.Translate([System.Security.Principal.NTAccount])

 $user.value

}

I decided to not do this, but rather to perform a direct LDAP call instead. The advantage of this is that it returns a DirectoryEntry class that can be easily used to perform user manipulation. The SidtoUser function shown above (in addition to not possessing a proper Windows PowerShell function name) only returns a string, and would therefore require additional processing to return a DirectoryEntry object.

The Get-SidToUser function, shown here, relies on the fact that the LDAP can return an object based upon its SID. This technique could be used via VBScript as well, because it is basic LDAP stuff. The Get-SidToUser function is shown here:

Function Get-SidToUser()

{

 New-Underline("Obtaining SID translation ... this might take a bit of time ...")

 New-UnderLine("Sid: $sid is:")

 [adsi]"LDAP://<SID=$sid>"

 exit

} #end get-sidToUser

KW, that is all there is to translating a user account name to a SID, and converting a SID back to a user account name. Active Directory Week will continue tomorrow.

We invite you to follow us on Twitter and Facebook. If you have any questions, send email to us at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

 

Ed Wilson and Craig Liebendorfer, Scripting Guys

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • The script worked for me when trying to translate a user.  However, when trying to translate an SID, I get the error message:

    format-default : The following exception occurred while retrieving member "PSComputerName": "There is no such object on the server."

    + CategoryInfo                 : NotSpecified: (:) [format-default], ExtendedTypeSystemException

    + FullyQualifiedErrorID    : CatchFromBaseGetMember,Microsoft.PowerShell.Commands.FormatDefaultCommand

  • Same here, it's only for objects that don't exist but I've got this running through a script enumerating a bunch of them and this error halts the script. Haven't been able to figure out how to get it to move past. Wrapped the [adsi]"LDAP://" in a Try with a Catch to just spit out "Invalid SID" but PS still kicks off the error and halts the script.

  • Here's what I used that seems to work much better. $sid comes from elsewhere in the script and is just the SID... $objSID = New-Object System.Security.Principal.SecurityIdentifier ` ("$sid") $objUser = $objSID.Translate( [System.Security.Principal.NTAccount]) $user = $objUser.Value " SID: $sid = $user" >> $LogFile

  • Well carriage returns don't display well... $objSID = New-Object System.Security.Principal.SecurityIdentifier ` ("$sid") $objUser = $objSID.Translate( [System.Security.Principal.NTAccount]) $user = $objUser.Value " SID: $sid = $user" >> $LogFile

  • Bah. You'll figure it out.

  • # demo of tranlation done correctly as an excercise. $stringSIDS=gwmi win32_account|%{$_.SID} $stringSIDS | ForEach-Object{ $stringSID=$_ $sid=New-Object System.Security.Principal.SecurityIdentifier ($stringSID) $ntaccount=$SID.Translate( [System.Security.Principal.NTAccount]) Write-Host "SID: $sid`:" -fore green -back blue -nonew Write-Host $ntaccount -fore white -back blue }

  • thanks

  • Hi Guys,

    This is an amazing script for SID to Username resolution. It runs well in a single domain forest . But, i have the requirement to run this script in a multidomain forest. With over 22 subdomains.

    Will I need to try to query the Global catalog to pull the information i need or is there any better methods. Plus, how do i query the Global Catalog , how do i prepare the query string.

  • $accountid='somaeaccountid'
    $forest=[system.directoryservices.activedirectory.Forest]::GetCurrentForest().Name
    $searcher=[adsisearcher]"samaccountname=$accountid"
    $searcher.SearchRoot="GC://$forest"
    $searcher.PropertiesToLoad.AddRange(@('name','objectsid','distinguishedname'))
    $results=$searcher.FindAll()
    foreach($account in $results){
    $binarySID=$account.Properties['objectsid'][0]
    $translate=New-Object System.Security.Principal.SecurityIdentifier($binarySID,0)
    $p=@{
    Name=$account.Properties['name'][0]
    SID=$translate.Value
    DistinguishedName=$account.Properties['distinguishedname'][0]
    }
    New-Object PsObject -Property $p
    }