As usually happens during my day, I get sidetracked to another issue, and before you know it, I have a new PowerShell tool. In this instance, I was looking at event logs using Get-WinEvent. One of the event record properties is a UserID.
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
That's very nice, but who is this? In this particular instance, the UserID property is SecurityIdentifier object.
To resolve the SID, you can use the Translate() method.
Nice. I can take this a step further to get a simple string.
$a.userid.Translate([system.security.principal.ntaccount]).value
But what if I only have the SID value as a string? You might encounter this in plain text log files.
How do I resolve this?
It is actually pretty easy. I can create a SecurityIdentifier object from the string.
[System.Security.Principal.SecurityIdentifier]::new($sid)
This gives me the object with the Translate() method.
It is not required, but I can resolve the SID as a string with a single expression.
[System.Security.Principal.SecurityIdentifier]::new($sid).Translate([system.security.principal.NTAccount]).value
Creating a PowerShell Tool
Naturally, the next step is to create a PowerShell tool to simplify the entire process and add a touch of extra value.
Function Resolve-SID {
[cmdletbinding()]
[OutputType("ResolvedSID", "String")]
Param(
[Parameter(
Position = 0,
Mandatory,
ValueFromPipeline,
ValueFromPipelineByPropertyName,
HelpMessage = "Enter a SID string."
)]
[ValidateScript({
If ($_ -match 'S-1-[1235]-\d{1,2}(-\d+)*') {
$True
}
else {
Throw "The parameter value does not match the pattern for a valid SID."
$False
}
})]
[string]$SID,
[Parameter(HelpMessage = "Display the resolved account name as a string.")]
[switch]$ToString
)
Begin {
Write-Verbose "[$((Get-Date).TimeofDay) BEGIN ] Starting $($myinvocation.mycommand)"
} #begin
Process {
Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Converting $SID "
Try {
if ($SID -eq 'S-1-5-32') {
#apparently you can't resolve the builtin account
$resolved = "$env:COMPUTERNAME\BUILTIN"
}
else {
$resolved = [System.Security.Principal.SecurityIdentifier]::new($sid).Translate([system.security.principal.NTAccount]).value
}
if ($ToString) {
$resolved
}
else {
if ($resolved -match "\\") {
$domain = $resolved.Split("\")[0]
$username = $resolved.Split("\")[1]
}
else {
$domain = $Null
$username = $resolved
}
[pscustomObject]@{
PSTypename = "ResolvedSID"
NTAccount = $resolved
Domain = $domain
Username = $username
SID = $SID
}
}
}
Catch {
Write-Warning "Failed to resolve $SID. $($_.Exception.InnerException.Message)"
}
} #process
End {
Write-Verbose "[$((Get-Date).TimeofDay) END ] Ending $($myinvocation.mycommand)"
} #end
} #close Resolve-SID
At the core, my function uses the one-line resolution code. I'm parsing the result to create a richer object. Although, I included a ToString parameter.
This makes it easier to use the result in your messaging or logging.
The other item of interest in the function is the parameter validation for the SID. I'm using a regular expression pattern to verify that the value looks like a SID.
The regular expression pattern should take into account built-in SIDs as well.
By the way, my function writes a typed object to the pipeline so you could create a format ps1xml file.
The only issue I encountered was resolving the BUILTIN SID of S-1-5-32. This was the only valid SID I found that my code failed to resolve. Instead, I handle this is with a simple IF statement.
if ($SID -eq 'S-1-5-32') {
#apparently you can't resolve the builtin account
$resolved = "$env:COMPUTERNAME\BUILTIN"
}
else {
$resolved = [System.Security.Principal.SecurityIdentifier]::new($sid).Translate([system.security.principal.NTAccount]).value
}
If there is any other error trying to resolve the SID, I'll handle it in the Catch block and write a warning.
I have no doubt there are other tools to resolve SIDs and my function is by no means the only PowerShell solution you'll find. I'd love to hear what you think.
Nice article as usual.
Instead of [System.Security.Principal.SecurityIdentifier]::new($sid).Translate([system.security.principal.NTAccount]).value
it seems that we can use [System.Security.Principal.SecurityIdentifier]::new($sid).Translate([system.security.principal.NTAccount]) only.
Probably. PowerShell is pretty good at figuring out what you want. But I wanted to ensure the result was a simple. Although, I could also have used System.Security.Principal.SecurityIdentifier]::new($sid).Translate([system.security.principal.NTAccount]).toString() to achieve the same end result.
Steps explained v clearly. However I keep running into the below error- The same script works well on a coworker’s system. Not sure what i’m missing here.
Exception calling “Translate” with “1” argument(s): “Some or all identity references could not be translated.”
At line:6 char:1
+ $name = ([System.Security.Principal.SecurityIdentifier] $nm.Name).Tra …
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : IdentityNotMappedException
You can run into a problem if the SID belongs to a domain and you are in a workgroup, or vice versa.