Security descriptors, part 2: principals, SIDs and PowerShell

<< Part 1 Part 3 >>

Now let's get to the code examples.

Dealing with the security descriptors requires dealing with the identities of users and groups (i.e. principals), and it's a pain. In a lot of places they have to be represented as SIDs in either the binary or text format, and these conversions are long, difficult, and just plain extremely annoying.

By comparison, on Unix the dealing with the user identity in the shell tools is easy: you just give the name or the UID (user ID) number, and they figure out things for themselves, and print back the symbolic user names whenever possible.

I want to have the same thing in PowerShell: just give the commands the principal identities in whatever form (things are more complex in PowerShell, with 4 formats available) and have them figure it out, and get back the symbolic names. The first thing to that is to define the commands that take the principal values in whatever format and convert it to the desired format. And then the other commands can use them. I'll show the code that does that, and you can either use these functions or look at their implementation and find out how to do these annoying conversions manually.

function ConvertTo-NTAccount
{
<#
.SYNOPSIS
Convert a string containing either the numeric SID or a symbolic
name, or a SID object to an NTAccount object.
#>
    param(
        ## Value to be converted. Accepts a string with either a numeric
        ## SID or a symbolic name, or a SID object, or an NTAccount object
        ## (in this case just returns as-is).
        $From
    )
    if ($From -is [System.Security.Principal.NTAccount]) {
        return $From
    }
    if ($From -is [System.Security.Principal.SecurityIdentifier]) {
        return ($From.Translate([System.Security.Principal.NTAccount]))
    }
    if (!($From -is [string])) {
        Throw "Don't know how to convert an object of type '$($From.GetType())' to an NTAccount"
    }
    try {
        # Try the symbolic format first.
        # For the symbolic format, translate twice, to make sure that
        # the value is valid.
        $acc = new-object System.Security.Principal.NTAccount($From)
        $sid = $acc.Translate([System.Security.Principal.SecurityIdentifier])
        return ($sid.Translate([System.Security.Principal.NTAccount]))
    } catch {
        $sid = new-object System.Security.Principal.SecurityIdentifier($From)
        return ($sid.Translate([System.Security.Principal.NTAccount]))
    }
}
Export-ModuleMember -Function ConvertTo-NTAccount

function ConvertTo-SID
{
<#
.SYNOPSIS
Convert a string containing either the numeric SID or a symbolic
name, or an NTAccount object to a SID object.
#>
    param(
        ## Value to be converted. Accepts a string with either a numeric
        ## SID or a symbolic name, or a SID object (in this case just returns as-is),
        ## or an NTAccount object.
        $From
    )
    if ($From -is [System.Security.Principal.SecurityIdentifier]) {
        return $From
    }
    if ($From -is [System.Security.Principal.NTAccount]) {
        return ($From.Translate([System.Security.Principal.SecurityIdentifier]))
    }
    if (!($From -is [string])) {
        Throw "Don't know how to convert an object of type '$($From.GetType())' to a SID"
    }
    try {
        # Try the numeric format first
        return (new-object System.Security.Principal.SecurityIdentifier($From))
    } catch {
        $acc = new-object System.Security.Principal.NTAccount($From)
        return ($acc.Translate([System.Security.Principal.SecurityIdentifier]))
    }
}
Export-ModuleMember -Function ConvertTo-SID

function ConvertTo-PrintSID
{
<#
.SYNOPSIS
Convert a SID representation to a printable string containing either the human-readable
name, or if that is not possible, the SID text.
#>
    param(
        ## Value to be converted. Accepts a string with either a numeric
        ## SID or a symbolic name, or a SID object (in this case just returns as-is),
        ## or an NTAccount object.
        $From
    )
    $sid = ConvertTo-SID $From
    try {
        return ($sid.Translate([System.Security.Principal.NTAccount]).Value)
    } catch {
        return ($sid.Value)
    }
}
Export-ModuleMember -Function ConvertTo-PrintSID

All these functions accept the principal identity in one of the four formats:

  • the symbolic name
  • the SID as a text string
  • the symbolic name as an NTAccount object
  • the SID as an object

They return the result in the requested format.

ConvertTo-NTAccount returns the NTAccount object. It's not a particularly useful function, since this format is not used much by itself. An interesting thing about this function is that it always performs the validation. Basically, any string can be placed into the NTaccount object without any validation. But this function makes sure that the name is valid and converted to the canonical format, doing the double conversion: first to the SID object and then back to the NTAccount object. Note that not all the SIDs can be converted to NTAccount. If a SID doesn't have a mapping to a symbolic name, the conversion will throw an exception.

ConvertTo-SID returns the SID object, and it's the typical format you'd want when building the other objects.

ConvertTo-PrintSID produces the text format in a smart way. It will be the symbolic principal name if possible, and otherwise the SID string. It's like the Unix tools printing the user name if possible, and otherwise the numeric user id (UID).

If you want to get specifically the symbolic name or the SID as a string, that can be done with (ConvertTo-NTAccount).Value or (ConvertTo-SID).Value.

<< Part 1  Part 3 >>