Skip to content

Commit

Permalink
Merge pull request #11 from LarryWisherMan/fix/RemoteContext
Browse files Browse the repository at this point in the history
Updated `Get-UserAccountFromSID` and `Get-SIDFromUsername`
  • Loading branch information
LarryWisherMan authored Oct 1, 2024
2 parents d6d172c + 086ac3b commit 6cff05f
Show file tree
Hide file tree
Showing 12 changed files with 314 additions and 119 deletions.
10 changes: 3 additions & 7 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,16 +76,9 @@ from the Windows registry based on SIDs, Usernames, or UserProfile objects.
- If the user has administrator privileges, the function retrieves user
profiles from the registry using `Get-SIDProfileInfo`.

- If the user lacks administrative privileges, the function falls back to the
`Get-SIDProfileInfoFallback` method, which retrieves user profiles using
CIM/WMI without requiring registry access.

- A warning is logged when the fallback method is used, indicating that special
system accounts are excluded.

- Refactored `Process-RegistryProfiles` to better account for access denied errors
when testing profile paths with `Test-FolderExists`.

- Updated `UserProfile` object creation in `Test-OrphanedProfile` for
`$AccessError` scenarios.

Expand All @@ -96,6 +89,9 @@ from the Windows registry based on SIDs, Usernames, or UserProfile objects.
(`System.Security.Principal.NTAccount` and `System.Security.Principal.SecurityIdentifier`)
instead of relying on `Get-CimInstance` for SID resolution.

- `Get-UserAccountFromSID` and `Get-SIDFromUsername` now invoke locally / Remotely
to resolve without null values

## [0.2.0] - 2024-09-12

### Added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ function Invoke-ProfileRegistryItemProcessing
$ParameterHash.IsSpecial = $IsSpecialResults.IsSpecial

# Translate SID to user account information
$accountInfo = Get-UserAccountFromSID -SID $Sid
$accountInfo = Get-UserAccountFromSID -SID $Sid -ComputerName $ComputerName -WarningAction SilentlyContinue
$ParameterHash.Domain = $accountInfo.Domain
$ParameterHash.UserName = $accountInfo.Username

Expand Down
107 changes: 83 additions & 24 deletions source/Private/Helpers/Get-SIDFromUsername.ps1
Original file line number Diff line number Diff line change
@@ -1,55 +1,114 @@
<#
.SYNOPSIS
Retrieves the Security Identifier (SID) for a given username.
Retrieves the Security Identifier (SID) for a given username either locally or remotely.
.DESCRIPTION
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`.
The `Get-SIDFromUsername` function resolves the Security Identifier (SID) associated with a given username by using the .NET `System.Security.Principal.NTAccount` class.
The function allows execution on a local or remote computer by leveraging PowerShell's `Invoke-Command`.
If the user exists and the SID can be resolved, the SID 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.
Specifies the username for which to retrieve the SID. This parameter is mandatory and must not be null or empty.
.PARAMETER ComputerName
Specifies the computer from which to retrieve the SID. If this parameter is not provided, the function will default to the local computer.
When provided, the function will attempt to retrieve the SID from the specified remote computer.
.OUTPUTS
String - The Security Identifier (SID) associated with the provided username.
If the SID cannot be resolved, the function returns `$null`.
.NOTES
This function uses the .NET `System.Security.Principal.NTAccount` class to resolve the username into a SID. It can query either the local system or a remote system (via PowerShell remoting).
If PowerShell remoting is disabled or the specified remote computer is unreachable, the function will issue a warning and return `$null`.
.EXAMPLE
Get-SIDFromUsername -Username 'JohnDoe'
Get-SIDFromUsername -Username 'JohnDoe'
Description:
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.
Description:
Retrieves the SID for the user 'JohnDoe' from the local computer. If the user exists and the SID is found, it will be returned.
.EXAMPLE
Get-SIDFromUsername -Username 'LocalAdmin'
Get-SIDFromUsername -Username 'JohnDoe' -ComputerName 'Server01'
Description:
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.
Description:
Retrieves the SID for the user 'JohnDoe' from the remote computer 'Server01'. If the user exists and the SID is found, it will be returned.
.NOTES
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.
#>
.EXAMPLE
Get-SIDFromUsername -Username 'Administrator'
Description:
Retrieves the SID for the 'Administrator' account from the local computer. This works for both local and domain accounts.
.EXAMPLE
Get-SIDFromUsername -Username 'Administrator' -ComputerName 'Server01'
Description:
Retrieves the SID for the 'Administrator' account from the remote computer 'Server01'. If the user account exists and the SID can be resolved, it will be returned.
.EXAMPLE
$sids = @('User1', 'User2') | ForEach-Object { Get-SIDFromUsername -Username $_ }
Description:
Retrieves the SIDs for multiple users by passing the usernames through the pipeline and invoking the function for each user.
.EXAMPLE
Get-SIDFromUsername -Username 'NonExistentUser'
Warning:
Failed to retrieve SID for username: NonExistentUser
Output:
$null
Description:
Attempts to retrieve the SID for a user that does not exist. In this case, the function issues a warning and returns `$null`.
#>
function Get-SIDFromUsername
{
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string]$Username
[string]$Username,

[Parameter(Mandatory = $false)]
[string]$ComputerName = $env:COMPUTERNAME # Default to the local computer
)

try
{
# Query WMI to get the SID for the given username
$ntAccount = New-Object System.Security.Principal.NTAccount($Username)

$SID = $ntAccount.Translate([System.Security.Principal.SecurityIdentifier])
# Define the script block for translating Username to SID
$scriptBlock = {
param ($Username)
try
{
$ntAccount = New-Object System.Security.Principal.NTAccount($Username)
$SID = $ntAccount.Translate([System.Security.Principal.SecurityIdentifier])

if ($Null -ne $SID -and $Null -ne $SID.Value)
{
return $SID.value
}
else
{
return $null
if ($null -ne $SID -and $null -ne $SID.Value)
{
return $SID.Value
}
else
{
return $null
}
}
catch
{
return $null
}
}

# Use Invoke-Command to run the script block locally or remotely
$result = Invoke-Command -ComputerName $ComputerName -ScriptBlock $scriptBlock -ArgumentList $Username

return $result
}
catch
{
Write-Warning "Failed to retrieve SID for username: $Username"
return $null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ function Get-ProcessedUserProfilesFromFolders
# Try to resolve SID
try
{
$SID = Resolve-UsernamesToSIDs -ComputerName -Usernames $userName -WarningAction SilentlyContinue
$SID = Resolve-UsernamesToSIDs -Usernames $userName -ComputerName $ComputerName
}
catch
{
Expand All @@ -98,7 +98,7 @@ function Get-ProcessedUserProfilesFromFolders
try
{
$TestSpecialParams.Add('SID', $SID)
$accountInfo = Get-UserAccountFromSID -SID $SID -WarningAction SilentlyContinue
$accountInfo = Get-UserAccountFromSID -SID $SID -ComputerName $ComputerName -WarningAction SilentlyContinue
$domain = $accountInfo.Domain
$userName = $accountInfo.Username
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ function Get-UserProfileLastUseTimeFromDat
# Extract the user folder path (everything before 'AppData\Local\Microsoft\Windows')
$userPath = [System.IO.Path]::GetDirectoryName([System.IO.Path]::GetDirectoryName([System.IO.Path]::GetDirectoryName([System.IO.Path]::GetDirectoryName([System.IO.Path]::GetDirectoryName($datFilePath)))))


# Extract the user name based on the user folder path
$userName = if ($isLocal)
{
Expand All @@ -106,7 +107,7 @@ function Get-UserProfileLastUseTimeFromDat
ComputerName = $ComputerName
Username = $userName
LastLogon = $lastLogon
UserPath = $userPath
UserPath = (Get-DirectoryPath -BasePath $userPath -IsLocal $true -ComputerName $ComputerName)
}
}
}
Expand Down
60 changes: 52 additions & 8 deletions source/Private/ProfRegProcessing/Get-UserAccountFromSID.ps1
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
<#
.SYNOPSIS
Retrieves the domain and username associated with a given Security Identifier (SID).
Retrieves the domain and username associated with a given Security Identifier (SID), locally or remotely.
.DESCRIPTION
The `Get-UserAccountFromSID` function takes a Security Identifier (SID) as input and translates it into a corresponding user account's domain and username. The function uses .NET's `System.Security.Principal.SecurityIdentifier` class to perform the translation and returns a custom object containing the SID, domain, and username.
The `Get-UserAccountFromSID` function takes a Security Identifier (SID) as input and translates it into a corresponding user account's domain and username. The function can be executed locally or remotely using PowerShell remoting.
If the SID cannot be translated, the function returns null for the domain and username and issues a warning.
The function uses .NET's `System.Security.Principal.SecurityIdentifier` class to perform the translation and returns a custom object containing the SID, domain, and username. If the SID cannot be translated, it returns null for the domain and username and issues a warning.
.PARAMETER SID
The Security Identifier (SID) to be translated. This is a required parameter and must not be null or empty. The function supports pipeline input for the SID.
.PARAMETER ComputerName
The name of the computer to perform the translation. If not specified, the function defaults to the local computer (`$env:COMPUTERNAME`).
When a remote computer is specified, the function uses `Invoke-Command` to run the translation remotely.
.OUTPUTS
PSCustomObject - An object with the following properties:
- SID: The input SID.
Expand All @@ -29,7 +33,18 @@
S-1-5-21-1234567890-1234567890-1234567890-1001 DOMAIN User
Description:
This example retrieves the domain and username associated with the given SID.
This example retrieves the domain and username associated with the given SID on the local computer.
.EXAMPLE
Get-UserAccountFromSID -SID 'S-1-5-21-1234567890-1234567890-1234567890-1001' -ComputerName 'RemoteServer01'
Output:
SID Domain Username
--- ------ --------
S-1-5-21-1234567890-1234567890-1234567890-1001 DOMAIN User
Description:
This example retrieves the domain and username associated with the given SID from the remote computer 'RemoteServer01'.
.EXAMPLE
'S-1-5-21-1234567890-1234567890-1234567890-1001' | Get-UserAccountFromSID
Expand Down Expand Up @@ -85,7 +100,10 @@ function Get-UserAccountFromSID
throw "Invalid SID format: $_"
}
})]
[string]$SID
[string]$SID,

[Parameter(Mandatory = $false)]
[string]$ComputerName = $env:COMPUTERNAME # Default to the local computer
)

begin
Expand All @@ -96,9 +114,35 @@ function Get-UserAccountFromSID
{
try
{
$ntAccount = New-Object System.Security.Principal.SecurityIdentifier($SID)
$userAccount = $ntAccount.Translate([System.Security.Principal.NTAccount])
$domain, $username = $userAccount.Value.Split('\', 2)
# Define the script block that performs the SID-to-account translation
$scriptBlock = {
param ($SID)
try
{
$ntAccount = New-Object System.Security.Principal.SecurityIdentifier($SID)
$userAccount = $ntAccount.Translate([System.Security.Principal.NTAccount])
$domain, $username = $userAccount.Value.Split('\', 2)
return [pscustomobject]@{
Domain = $domain
Username = $username
}
}
catch
{
Write-Warning "Failed to translate SID: $SID"
return [pscustomobject]@{
Domain = $null
Username = $null
}
}
}

# Invoke the command locally or remotely
$result = Invoke-Command -ComputerName $ComputerName -ScriptBlock $scriptBlock -ArgumentList $SID

# Assign the returned result to variables
$domain = $result.Domain
$username = $result.Username
}
catch
{
Expand Down
27 changes: 19 additions & 8 deletions source/Private/RemoveProfileReg/Resolve-UsernamesToSIDs.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,37 @@
Resolves a list of usernames to their corresponding Security Identifiers (SIDs).
.DESCRIPTION
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.
The `Resolve-UsernamesToSIDs` function resolves each provided username to its corresponding Security Identifier (SID) on a specified computer or the local machine. It uses the `Get-SIDFromUsername` function, which can resolve usernames to SIDs either locally or remotely. For each username, the function attempts to resolve the username on the specified computer. 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. This parameter is mandatory.
.PARAMETER ComputerName
Specifies the name of the computer on which to resolve the usernames to SIDs. If not provided, the function defaults to the local computer.
.EXAMPLE
Resolve-UsernamesToSIDs -Usernames 'user1', 'user2'
Description:
Resolves the SIDs for 'user1' and 'user2' on the local computer.
.EXAMPLE
Resolve-UsernamesToSIDs -Usernames 'user1', 'user2' -ComputerName 'Server01'
Description:
Resolves the SIDs for 'user1' and 'user2' on the remote computer 'Server01'.
.OUTPUTS
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.
Array of custom objects containing the username and the corresponding SID. If a username cannot be resolved, the SID will be null, 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.
This function supports resolving SIDs on remote computers using the `ComputerName` parameter.
#>
function Resolve-UsernamesToSIDs
{
param (
[string[]]$Usernames
[string[]]$Usernames,
[string]$ComputerName = $env:COMPUTERNAME
)

$SIDs = @()
Expand All @@ -32,18 +42,19 @@ function Resolve-UsernamesToSIDs
{
try
{
$SID = Get-SIDFromUsername -Username $Username
$SID = Get-SIDFromUsername -Username $Username -ComputerName $ComputerName
}
catch {}
if ($Null -ne $SID -and $Null -ne $SID)

# Ensure $SID is not $null before adding to $SIDs array
if ($SID)
{
$SIDs += $SID
}
else
{
Write-Warning "Could not resolve SID for username $Username."
Write-Verbose "Could not resolve SID for username $Username."
}

}

return $SIDs
Expand Down
2 changes: 1 addition & 1 deletion source/Public/Get-UserProfilesFromFolders.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ function Get-UserProfilesFromFolders
return @() # Return an empty array
}

Get-ProcessedUserProfilesFromFolders -UserFolders $UserFolders -ComputerName $ComputerName
Get-ProcessedUserProfilesFromFolders -UserFolders $UserFolders -ComputerName $ComputerName

}
catch
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 @@ -95,7 +95,7 @@ function Remove-UserProfilesFromRegistry
# Resolve SIDs if Usernames are provided
if ($PSCmdlet.ParameterSetName -eq 'UserNameSet')
{
$SIDs = Resolve-UsernamesToSIDs -Usernames $Usernames
$SIDs = Resolve-UsernamesToSIDs -Usernames $Usernames -ComputerName $ComputerName

# If no SIDs were resolved, stop execution by throwing a terminating error
if (-not $SIDs)
Expand Down
Loading

0 comments on commit 6cff05f

Please sign in to comment.