Public/Get-User.ps1

function Get-User {
  <#
  .SYNOPSIS
    Gets local user accounts from the Windows Security Accounts Manager.
  .DESCRIPTION
    The Get-User cmdlet gets local user accounts from the Windows Security
    Accounts Manager. This includes local accounts that have been connected to a Microsoft account.
  .PARAMETER Name
    Specifies the local user accounts to get from the local Security Accounts Manager.
    This accepts a name or wildcard string.
  .PARAMETER SID
    Specifies a user from the local Security Accounts Manager by SecurityIdentifier.
  .EXAMPLE
    Get-User
    Gets all local users.
  .EXAMPLE
    Get-User -Name "Admin"
    Gets the local user named Admin.
  #>

  [CmdletBinding(DefaultParameterSetName = 'Default')]
  param(
    [Parameter(Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Default')]
    [ArgumentCompleter({
        [OutputType([System.Management.Automation.CompletionResult])]
        param(
          [string] $CommandName,
          [string] $ParameterName,
          [string] $WordToComplete,
          [System.Management.Automation.Language.CommandAst] $CommandAst,
          [System.Collections.IDictionary] $FakeBoundParameters
        )
        $CompletionResults = [System.Collections.Generic.List[CompletionResult]]::new()
        $matchingNames = [LocalAccountHelper]::GetAllLocalUsers().Where({ $_.Name -like "$WordToComplete*" })
        foreach ($n in $matchingNames) { $CompletionResults.Add([System.Management.Automation.CompletionResult]::new($n.Name)) }
        return $CompletionResults
      })]
    [string[]]$Name,

    [Parameter(Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'SecurityIdentifier')]
    [System.Security.Principal.SecurityIdentifier[]]$SID
  )

  process {
    try {
      # If no parameters, get all users
      if ($null -eq $Name -and $null -eq $SID) {
        return [LocalAccountHelper]::GetAllLocalUsers()
      }

      # Process by name
      if ($null -ne $Name) {
        foreach ($userName in $Name) {
          if ([string]::IsNullOrWhiteSpace($userName)) { continue }

          # Try to parse as SID first
          $sid = [LocalAccountHelper]::TryParseSid($userName)
          if ($null -ne $sid) {
            $user = [LocalAccountHelper]::GetLocalUserBySid($sid)
            if ($null -ne $user) {
              $user
            } else {
              $ex = [UserNotFoundException]::new($userName, $userName)
              Write-Error -Message $ex.Message -ErrorId 'UserNotFound' -Category ObjectNotFound -TargetObject $userName
            }
          } else {
            # Check for wildcard
            if ($userName -match '\*|\?') {
              $pattern = [System.Management.Automation.WildcardPattern]::new($userName, [System.Management.Automation.WildcardOptions]::Compiled -bor [System.Management.Automation.WildcardOptions]::IgnoreCase)
              $allUsers = [LocalAccountHelper]::GetAllLocalUsers()
              foreach ($u in $allUsers) {
                if ($pattern.IsMatch($u.Name)) {
                  $u
                }
              }
            } else {
              $user = [LocalAccountHelper]::GetLocalUserByName($userName)
              if ($null -ne $user) {
                $user
              } else {
                $ex = [UserNotFoundException]::new($userName, $userName)
                Write-Error -Message $ex.Message -ErrorId 'UserNotFound' -Category ObjectNotFound -TargetObject $userName
              }
            }
          }
        }
      }

      # Process by SID
      if ($null -ne $SID) {
        foreach ($userSid in $SID) {
          if ($null -eq $userSid) { continue }
          $user = [LocalAccountHelper]::GetLocalUserBySid($userSid)
          if ($null -ne $user) {
            $user
          } else {
            $ex = [UserNotFoundException]::new($userSid.Value, $userSid)
            Write-Error -Message $ex.Message -ErrorId 'UserNotFound' -Category ObjectNotFound -TargetObject $userSid
          }
        }
      }
    } catch {
      # Let the specific error IDs propagate up without overwriting
      throw
    }
  }
}