Skip to content

Commit

Permalink
Merge pull request #8 from LarryWisherMan/feature/ResolveSIDFromUserName
Browse files Browse the repository at this point in the history
changed `Get-SIDFromUsername` to use .net class for resolution
  • Loading branch information
LarryWisherMan authored Sep 24, 2024
2 parents ac68f80 + 2079e29 commit 71a8f9b
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 116 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ from the Windows registry based on SIDs, Usernames, or UserProfile objects.
- The module is now using `WinRegOps` version `0.4.0` for more refined registry
value retrieval.

- Refactored `Get-SIDFromUsername` to use `.NET` classes
(`System.Security.Principal.NTAccount` and `System.Security.Principal.SecurityIdentifier`)
instead of relying on `Get-CimInstance` for SID resolution.

## [0.2.0] - 2024-09-12

### Added
Expand Down
38 changes: 14 additions & 24 deletions source/Private/Helpers/Get-SIDFromUsername.ps1
Original file line number Diff line number Diff line change
@@ -1,67 +1,57 @@
<#
.SYNOPSIS
Retrieves the Security Identifier (SID) for a given username from a specified computer, defaulting to the local computer if no computer name is provided.
Retrieves the Security Identifier (SID) for a given username.
.DESCRIPTION
The `Get-SIDFromUsername` function queries the specified computer using WMI (CIM) to retrieve the SID associated with a given username. If the `ComputerName` parameter is not provided, the function defaults to the local computer. The function uses the `Get-CimInstance` cmdlet to perform the lookup on the remote or local computer. If the user exists and the SID is found, it is returned. If no SID is found or an error occurs, a warning message is displayed, and the function returns `$null`.
The `Get-SIDFromUsername` function resolves the Security Identifier (SID) associated with a given username using the .NET `System.Security.Principal.NTAccount` class. The function translates the provided username into a SID by querying the local system. If the user exists and the SID can be resolved, it is returned. Otherwise, a warning is displayed, and the function returns `$null`.
.PARAMETER Username
Specifies the username for which to retrieve the SID. This parameter is mandatory.
.PARAMETER ComputerName
Specifies the name of the computer where the user account exists. This parameter is optional and defaults to the local computer (`localhost`). You can specify either a local or remote computer.
.EXAMPLE
Get-SIDFromUsername -Username 'JohnDoe' -ComputerName 'Server01'
Get-SIDFromUsername -Username 'JohnDoe'
Description:
This command retrieves the SID for the user 'JohnDoe' from the computer 'Server01'. If the user exists on the computer and has a SID, it will be returned; otherwise, a warning will be displayed.
This command retrieves the SID for the user 'JohnDoe' from the local computer. If the user exists and the SID is found, it is returned; otherwise, a warning will be displayed.
.EXAMPLE
Get-SIDFromUsername -Username 'LocalAdmin'
Description:
This command retrieves the SID for the user 'LocalAdmin' from the local computer (localhost) since no `ComputerName` is provided. If the user exists on the local computer and has a SID, it will be returned; otherwise, a warning will be displayed.
.EXAMPLE
Get-SIDFromUsername -Username 'DomainUser' -ComputerName 'DomainController'
Description:
This command retrieves the SID for the user 'DomainUser' from the remote computer 'DomainController'. If the user exists on the specified computer and has a SID, it will be returned; otherwise, a warning will be displayed.
This command retrieves the SID for the user 'LocalAdmin' from the local computer. If the user exists and the SID is found, it is returned; otherwise, a warning will be displayed.
.NOTES
If the `ComputerName` is not provided, it defaults to the local computer.
This function does not use WMI or CIM for querying user information, but rather the .NET `System.Security.Principal.NTAccount` class, which directly translates the username to a SID. As a result, this function works for both local and domain accounts if the appropriate access is available.
#>

function Get-SIDFromUsername
{
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string]$Username,

[Parameter(Mandatory = $false)]
[string]$ComputerName = $env:COMPUTERNAME
[string]$Username
)

try
{
# Query WMI to get the SID for the given username
$userAccount = Get-CimInstance -Class Win32_UserAccount -ComputerName $ComputerName -Filter "Name = '$Username'"
$ntAccount = New-Object System.Security.Principal.NTAccount($Username)

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

if ($userAccount -and $userAccount.SID)
if ($Null -ne $SID -and $Null -ne $SID.Value)
{
return $userAccount.SID
return $SID.value
}
else
{
Write-Warning "Could not find SID for username $Username on $ComputerName."
Write-Warning "Could not find SID for username $Username."
return $null
}
}
catch
{
Write-Warning "An error occurred while trying to resolve SID for username $Username on $ComputerName. Error: $_"
Write-Warning "An error occurred while trying to resolve SID for username $Username . Error: $_"
return $null
}
}
24 changes: 11 additions & 13 deletions source/Private/RemoveProfileReg/Resolve-UsernamesToSIDs.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -3,42 +3,40 @@
Resolves a list of usernames to their corresponding Security Identifiers (SIDs).
.DESCRIPTION
The `Resolve-UsernamesToSIDs` function resolves each provided username to its corresponding SID on the specified computer. If a username cannot be resolved, a warning is logged.
The `Resolve-UsernamesToSIDs` function resolves each provided username to its corresponding Security Identifier (SID) using the .NET `System.Security.Principal.NTAccount` class. For each username in the input array, the function attempts to resolve the username locally. If a username cannot be resolved, a warning is logged, and the function continues processing the next username.
.PARAMETER Usernames
Specifies an array of usernames to resolve to SIDs.
.PARAMETER ComputerName
Specifies the name of the computer on which to resolve the usernames.
Specifies an array of usernames to resolve to SIDs. This parameter is mandatory.
.EXAMPLE
Resolve-UsernamesToSIDs -Usernames 'user1', 'user2' -ComputerName 'Server01'
Resolve-UsernamesToSIDs -Usernames 'user1', 'user2'
Description:
Resolves the SIDs for 'user1' and 'user2' on Server01.
Resolves the SIDs for 'user1' and 'user2' on the local computer.
.OUTPUTS
Array of SIDs corresponding to the provided usernames.
#>
Array of SIDs corresponding to the provided usernames. If a username cannot be resolved, it will not be included in the output array, and a warning will be logged.
.NOTES
This function uses the `Get-SIDFromUsername` function, which internally uses the .NET `System.Security.Principal.NTAccount` class for resolving SIDs. It does not support resolving SIDs from remote computers and works only on the local system.
#>
function Resolve-UsernamesToSIDs
{
param (
[string[]]$Usernames,
[string]$ComputerName
[string[]]$Usernames
)

$SIDs = @()
foreach ($Username in $Usernames)
{
$SID = Get-SIDFromUsername -Username $Username -ComputerName $ComputerName
$SID = Get-SIDFromUsername -Username $Username
if ($SID)
{
$SIDs += $SID
}
else
{
Write-Warning "Could not resolve SID for username $Username on $ComputerName."
Write-Warning "Could not resolve SID for username $Username."
}
}
return $SIDs
Expand Down
2 changes: 1 addition & 1 deletion source/Public/Remove-UserProfilesFromRegistry.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ function Remove-UserProfilesFromRegistry
# Resolve SIDs if Usernames are provided
if ($PSCmdlet.ParameterSetName -eq 'UserNameSet')
{
$SIDs = Resolve-UsernamesToSIDs -Usernames $Usernames -ComputerName $ComputerName
$SIDs = Resolve-UsernamesToSIDs -Usernames $Usernames

# If no SIDs were resolved, return early
if (-not $SIDs)
Expand Down
82 changes: 22 additions & 60 deletions tests/Unit/Private/Helpers/Get-SIDFromUsername.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,22 @@ AfterAll {
Get-Module -Name $script:dscModuleName -All | Remove-Module -Force
}

Describe 'Get-SIDFromUsername' -Tags "Pivate", "Helpers" {
# Mock the Get-CimInstance cmdlet to simulate different scenarios
Describe 'Get-SIDFromUsername' -Tags "Private", "Helpers" {

Context 'When the username exists and has a valid SID' {
It 'should return the correct SID' {

InModuleScope -ScriptBlock {

$ComputerName = 'Server01'
# Mock Get-CimInstance to return a valid SID
Mock -CommandName Get-CimInstance -MockWith {
@{
SID = 'S-1-5-21-1234567890-1234567890-1234567890-1001'
# Mock NTAccount and SecurityIdentifier
Mock -CommandName New-Object -MockWith {
New-MockObject -Type 'System.Security.Principal.NTAccount' -Methods @{
Translate = { New-MockObject -Type 'System.Security.Principal.SecurityIdentifier' -Properties @{ Value = 'S-1-5-21-1234567890-1234567890-1234567890-1001' } }
}
}

# Act: Call the function
$result = Get-SIDFromUsername -Username 'JohnDoe' -ComputerName $ComputerName
$result = Get-SIDFromUsername -Username 'JohnDoe'

# Assert: Verify the result is the correct SID
$result | Should -Be 'S-1-5-21-1234567890-1234567890-1234567890-1001'
Expand All @@ -46,16 +44,17 @@ Describe 'Get-SIDFromUsername' -Tags "Pivate", "Helpers" {
It 'should return null and show a warning' {

InModuleScope -ScriptBlock {
$ComputerName = 'Server01'

# Mock Get-CimInstance to return null (user not found)
Mock -CommandName Get-CimInstance -MockWith { $null }
# Mock NTAccount to throw an error (user not found)
Mock -CommandName New-Object -MockWith {
throw [System.Security.Principal.IdentityNotMappedException]::new("User not found")
}

# Mock Write-Warning to capture the warning message
Mock -CommandName Write-Warning

# Act: Call the function
$result = Get-SIDFromUsername -Username 'NonExistentUser' -ComputerName $ComputerName
$result = Get-SIDFromUsername -Username 'NonExistentUser'

# Assert: The result should be null
$result | Should -BeNullOrEmpty
Expand All @@ -66,60 +65,27 @@ Describe 'Get-SIDFromUsername' -Tags "Pivate", "Helpers" {
}
}

Context 'When an error occurs while querying' {
Context 'When an error occurs while resolving the username' {
It 'should return null and display a warning with error information' {

InModuleScope -ScriptBlock {
$ComputerName = 'Server01'

# Mock Get-CimInstance to throw an exception
Mock -CommandName Get-CimInstance -MockWith { throw "WMI query failed" }
# Mock NTAccount to throw a general exception
Mock -CommandName New-Object -MockWith {
throw "An unexpected error occurred"
}

# Mock Write-Warning to capture the warning message
Mock -CommandName Write-Warning

# Act: Call the function
$result = Get-SIDFromUsername -Username 'JohnDoe' -ComputerName $ComputerName
$result = Get-SIDFromUsername -Username 'JohnDoe'

# Assert: The result should be null
$result | Should -BeNullOrEmpty

# Assert: Verify that the warning message was displayed
Assert-MockCalled -CommandName Write-Warning -Exactly 1 -Scope It

}
}
}

Context 'When mandatory parameters are missing' {
It 'should throw a missing parameter error for Username' {

InModuleScope -ScriptBlock {

$ComputerName = 'Server01'
# Act & Assert: Expecting the function to throw an error
{ Get-SIDFromUsername -Username $Null -ComputerName $ComputerName } | Should -Throw

}
}

It 'should default to localhost when ComputerName is missing' {
InModuleScope -ScriptBlock {
# Mock Get-CimInstance to return a valid SID when queried with 'localhost'
Mock -CommandName Get-CimInstance -MockWith {
@{
SID = 'S-1-5-21-1234567890-1234567890-1234567890-1001'
}
}

# Act: Call the function without providing ComputerName
$result = Get-SIDFromUsername -Username 'JohnDoe'

# Assert: The result should match the mock SID
$result | Should -Be 'S-1-5-21-1234567890-1234567890-1234567890-1001'

# Assert: Ensure Get-CimInstance was called with 'localhost'
Assert-MockCalled -CommandName Get-CimInstance -ParameterFilter { $ComputerName -eq $env:COMPUTERNAME } -Scope It
}
}
}
Expand All @@ -129,28 +95,24 @@ Describe 'Get-SIDFromUsername' -Tags "Pivate", "Helpers" {

InModuleScope -ScriptBlock {


# Mock Get-CimInstance to return an object without SID
Mock -CommandName Get-CimInstance -MockWith {
@{
SID = $null
# Mock NTAccount to return null for SID
Mock -CommandName New-Object -MockWith {
New-MockObject -Type 'System.Security.Principal.NTAccount' -Methods @{
Translate = { $null }
}
}

# Mock Write-Warning to capture the warning message
Mock -CommandName Write-Warning

$computerName = 'Server01'

# Act: Call the function
$result = Get-SIDFromUsername -Username 'JohnDoe' -ComputerName $computerName
$result = Get-SIDFromUsername -Username 'JohnDoe'

# Assert: The result should be null
$result | Should -BeNullOrEmpty

# Assert: Verify that the warning message was displayed
Assert-MockCalled -CommandName Write-Warning -Exactly 1 -Scope It

}
}
}
Expand Down
Loading

0 comments on commit 71a8f9b

Please sign in to comment.