user.ps1

function Get-UserInfo {
  <#
    .SYNOPSIS
      Get-UserInfo - Retrieves information about the current user, including their username, administrator status, and SID.
    .DESCRIPTION
      This function gathers details about the currently logged-in user by accessing the WindowsIdentity class. It checks if the user has administrator privileges and returns a hashtable containing the username, administrator status, and SID of the user.
    .EXAMPLE
      PS> Get-UserInfo
    .LINK
      https://github.com/adnoctem/winkit/lib/user.ps1
    .NOTES
      Author: MVProwess <info@mvprowess.com>
      License: MIT
  #>


  [OutputType([hashtable])]
  param ()

  $user = [System.Security.Principal.WindowsIdentity]::GetCurrent()
  $isAdmin = (New-Object System.Security.Principal.WindowsPrincipal($user)).IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)

  return @{
    UserName = $user.Name
    IsAdministrator = $isAdmin
    SID = $user.User.Value
  }
}

function Get-UserSID {
  <#
    .SYNOPSIS
      Get-UserSID - Retrieves the Security Identifier (SID) for a specified user.
    .DESCRIPTION
      This function takes a username as input and attempts to retrieve the corresponding SID by creating an NTAccount object and translating it to a SecurityIdentifier. If the user is not found, it returns null and logs an error message.
    .PARAMETER UserName
      The username for which to retrieve the SID (e.g., "DOMAIN\Username").
    .EXAMPLE
      PS> Get-UserSID -UserName "DOMAIN\Username"
    .LINK
      https://github.com/adnoctem/winkit/lib/user.ps1
    .NOTES
      Author: MVProwess <info@mvprowess.com>
      License: MIT
  #>


  [OutputType([string])]
  param (
    [Parameter(Mandatory = $true)]
    [string]$UserName
  )

  try {
    $user = New-Object System.Security.Principal.NTAccount($UserName)
    $sid = $user.Translate([System.Security.Principal.SecurityIdentifier])
    return $sid.Value
  }
  catch {
    Write-Error "Could not find SID for user '$UserName'. $_"
    return $null
  }
}

function Test-ADCredential {
  <#
    .SYNOPSIS
      Test-ADCredential - Validates a username/password pair against AD.
    .DESCRIPTION
      Simulates an authentication request against the domain using
      System.DirectoryServices.AccountManagement.PrincipalContext.
      ValidateCredentials, without opening a session. Returns $true only when
      both the username and password are valid.
 
      The domain is auto-detected from the credential when not specified.
      Accepts pipeline input of one or more credentials.
    .PARAMETER Credential
      The credential to validate.
    .PARAMETER Domain
      Domain to validate against. Defaults to the credential's own domain.
    .OUTPUTS
      bool
    .EXAMPLE
      PS> Test-ADCredential -Credential (Get-Credential)
    .LINK
      https://github.com/adnoctem/winkit/lib/user.ps1
    .NOTES
      Author: MVProwess <info@mvprowess.com>
      License: MIT
  #>


  [OutputType([bool])]
  [CmdletBinding()]
  param (
    [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
    [Alias('PSCredential')]
    [ValidateNotNull()]
    [System.Management.Automation.PSCredential]
    $Credential,

    [Parameter(Mandatory = $false)]
    [string]
    $Domain
  )

  begin {
    try {
      Add-Type -AssemblyName System.DirectoryServices.AccountManagement -ErrorAction Stop
    }
    catch {
      throw 'Test-ADCredential requires the System.DirectoryServices.AccountManagement assembly.'
    }

    if ([string]::IsNullOrWhiteSpace($Domain)) {
      $Domain = $Credential.GetNetworkCredential().Domain
    }

    $principalContext = [System.DirectoryServices.AccountManagement.PrincipalContext]::new(
      [System.DirectoryServices.AccountManagement.ContextType]::Domain, $Domain)
  }

  process {
    foreach ($item in $Credential) {
      $networkCredential = $item.GetNetworkCredential()
      try {
        [bool]$principalContext.ValidateCredentials($networkCredential.UserName, $networkCredential.Password)
      }
      catch {
        Write-Verbose "Credential validation failed: $($_.Exception.Message)"
        $false
      }
    }
  }

  end {
    $principalContext.Dispose()
  }
}

function Get-ADAccountLockoutSource {
  <#
    .SYNOPSIS
      Get-ADAccountLockoutSource - Reports the source of account lockouts from the PDC.
    .DESCRIPTION
      Dynamically resolves the PDC emulator for the domain (never hardcoded)
      and queries its Security event log for Event ID 4740 (account locked
      out), reporting which account was locked, when, and the ClientName of
      the machine that triggered the lockout. The ClientName pinpoints the
      device with stale cached credentials that is repeatedly failing auth.
    .PARAMETER DomainName
      Domain to query. Defaults to the current user's domain.
    .PARAMETER UserName
      Account filter (wildcards supported). Defaults to '*'.
    .PARAMETER StartTime
      Only report lockouts at or after this time. Defaults to the last 24
      hours.
    .PARAMETER Credential
      Optional alternative credential for the PDC query.
    .OUTPUTS
      One object per lockout event: TimeCreated, UserName, ClientName.
    .EXAMPLE
      PS> Get-ADAccountLockoutSource -UserName 'svc-backup' -StartTime (Get-Date).AddDays(-7)
    .LINK
      https://github.com/adnoctem/winkit/lib/user.ps1
    .NOTES
      Author: MVProwess <info@mvprowess.com>
      License: MIT
  #>


  [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '', Justification = 'UserName and StartTime are consumed by the remote scriptblock via the $Using: scope modifier.')]
  [OutputType([PSCustomObject])]
  [CmdletBinding()]
  param (
    [Parameter(Mandatory = $false)]
    [string]
    $DomainName = $env:USERDOMAIN,

    [Parameter(Mandatory = $false)]
    [ValidateNotNullOrEmpty()]
    [string]
    $UserName = '*',

    [Parameter(Mandatory = $false)]
    [datetime]
    $StartTime = (Get-Date).AddDays(-1),

    [Parameter(Mandatory = $false)]
    [System.Management.Automation.PSCredential]
    $Credential
  )

  begin {
    function Resolve-PDCServer {
      param (
        [string]$Domain = $env:USERDOMAIN,
        [System.Management.Automation.PSCredential]$Credential
      )

      if ($Credential) {
        $context = [System.DirectoryServices.ActiveDirectory.DirectoryContext]::new(
          'Domain', $Domain, $Credential.UserName, $Credential.GetNetworkCredential().Password)
      }
      else {
        $context = [System.DirectoryServices.ActiveDirectory.DirectoryContext]::new('Domain', $Domain)
      }

      [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($context).PdcRoleOwner.Name
    }

    try {
      $pdc = Resolve-PDCServer -Domain $DomainName -Credential $Credential
      Write-Verbose "PDC emulator resolved: $pdc"
    }
    catch {
      $PSCmdlet.ThrowTerminatingError($_)
    }
  }

  process {
    $properties = @(
      'TimeCreated',
      @{ Label = 'UserName'; Expression = { $_.Properties[0].Value } },
      @{ Label = 'ClientName'; Expression = { $_.Properties[1].Value } }
    )

    $invokeParams = @{
      ComputerName = $pdc
      ScriptBlock = {
        $props = @(
          'TimeCreated',
          @{ Label = 'UserName'; Expression = { $_.Properties[0].Value } },
          @{ Label = 'ClientName'; Expression = { $_.Properties[1].Value } }
        )
        Get-WinEvent -FilterHashtable @{ LogName = 'Security'; Id = 4740; StartTime = $Using:StartTime } |
          Where-Object { $_.Properties[0].Value -like $Using:UserName } |
          Select-Object -Property $props
      }
      ErrorAction = 'Stop'
    }
    if ($Credential) {
      $invokeParams.Credential = $Credential
    }

    try {
      Invoke-Command @invokeParams | Select-Object -Property $properties
    }
    catch {
      $PSCmdlet.ThrowTerminatingError($_)
    }
  }
}

function Get-ADFSMORoleHolder {
  <#
    .SYNOPSIS
      Get-ADFSMORoleHolder - Reports all five FSMO role holders in one call.
    .DESCRIPTION
      Queries the forest and domain for the Schema Master, Domain Naming
      Master, Infrastructure Master, RID Master, and PDC Emulator role
      holders. Loads the ActiveDirectory module if it is not already loaded.
    .PARAMETER Credential
      Optional alternative credential for the queries.
    .OUTPUTS
      PSCustomObject with SchemaMaster, DomainNamingMaster,
      InfrastructureMaster, RIDMaster, and PDCEmulator.
    .EXAMPLE
      PS> Get-ADFSMORoleHolder
    .LINK
      https://github.com/adnoctem/winkit/lib/user.ps1
    .NOTES
      Author: MVProwess <info@mvprowess.com>
      License: MIT
  #>


  [OutputType([PSCustomObject])]
  [CmdletBinding()]
  param (
    [Parameter(Mandatory = $false)]
    [Alias('RunAs')]
    [System.Management.Automation.PSCredential]
    $Credential
  )

  try {
    if (-not (Get-Module -Name ActiveDirectory -ErrorAction SilentlyContinue)) {
      Import-Module -Name ActiveDirectory -ErrorAction Stop
    }

    if ($Credential) {
      $forestRoles = Get-ADForest -Credential $Credential -ErrorAction Stop
      $domainRoles = Get-ADDomain -Credential $Credential -ErrorAction Stop
    }
    else {
      $forestRoles = Get-ADForest -ErrorAction Stop
      $domainRoles = Get-ADDomain -ErrorAction Stop
    }

    [PSCustomObject]@{
      SchemaMaster = $forestRoles.SchemaMaster
      DomainNamingMaster = $forestRoles.DomainNamingMaster
      InfrastructureMaster = $domainRoles.InfrastructureMaster
      RIDMaster = $domainRoles.RIDMaster
      PDCEmulator = $domainRoles.PDCEmulator
    }
  }
  catch {
    $PSCmdlet.ThrowTerminatingError($_)
  }
}