Public/Set-User.ps1

function Set-User {
  <#
  .SYNOPSIS
    Changes the properties of a user account.
  .DESCRIPTION
    The Set-User cmdlet changes the properties of a user account in the
    local Windows Security Accounts Manager. It can also reset the password.
  .PARAMETER InputObject
    Specifies the local user account to modify.
  .PARAMETER Name
    Specifies the local user account to change.
  .PARAMETER SID
    Specifies a user from the local Security Accounts Manager by SecurityIdentifier.
  .PARAMETER Description
    A descriptive comment for this user account.
  .PARAMETER FullName
    Specifies the full name of the user account.
  .PARAMETER Password
    Specifies the password for the local user account.
  .PARAMETER PasswordNeverExpires
    Specifies that the password will not expire.
  .PARAMETER UserMayChangePassword
    Specifies whether the user is allowed to change the password.
  .PARAMETER AccountExpires
    Specifies when the user account will expire.
  .PARAMETER AccountNeverExpires
    Specifies that the account will not expire.
  .PARAMETER Visibility
    Specifies whether the user account is visible on the logon screen.
    Hidden - User is hidden from the logon screen
    Visible - User is explicitly visible on the logon screen
  .PARAMETER Interactive
    Shows an interactive menu to set user visibility and other properties.
  .EXAMPLE
    Set-User -Name "John" -Description "New description"
    Sets the description for the local user named John.
  .EXAMPLE
    Set-User -Name "John" Hidden
    Hides the local user named John from the logon screen.
  .EXAMPLE
    Set-User -Name "John" -Interactive
    Shows an interactive menu to modify user properties.
  #>

  [CmdletBinding(SupportsShouldProcess = $true, DefaultParameterSetName = 'Name')]
  param(
    [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'InputObject')]
    [LocalUser]$InputObject,

    [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Name')]
    [string]$Name,

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

    [Parameter(ValueFromPipelineByPropertyName = $true)]
    [ValidateSet('Hidden', 'Visible', 'Unset')]
    [string]$Visibility,

    [Parameter(ValueFromPipelineByPropertyName = $true)]
    [string]$FullName,

    [Parameter(ValueFromPipelineByPropertyName = $true)]
    [System.Security.SecureString]$Password,

    [Parameter(ValueFromPipelineByPropertyName = $true)]
    [string]$Description,

    [Parameter(ValueFromPipelineByPropertyName = $true)]
    [bool]$PasswordNeverExpires,

    [Parameter(ValueFromPipelineByPropertyName = $true)]
    [bool]$UserMayChangePassword = $true,

    [Parameter(ValueFromPipelineByPropertyName = $true)]
    [DateTime]$AccountExpires = [DateTime]::MinValue,

    [Parameter(ValueFromPipelineByPropertyName = $true)]
    [switch]$AccountNeverExpires,

    [Parameter(ValueFromPipelineByPropertyName = $true)]
    [switch]$Interactive,

    [Parameter()]
    [switch]$Force
  )

  process {
    # Handle Interactive mode
    if ($Interactive) {
      $visibilityManager = [SpecialAccountHelper]::new()

      # Get the username to modify
      $targetUserName = $null
      if ($null -ne $InputObject) {
        $targetUserName = $InputObject.Name
      } elseif ($null -ne $Name) {
        $targetUserName = $Name
      } elseif ($null -ne $SID) {
        # Need to look up the user by SID
        $foundUser = Get-User -SID $SID -ErrorAction SilentlyContinue
        if ($foundUser) {
          $targetUserName = $foundUser.Name
        }
      }

      if ($null -eq $targetUserName) {
        Write-Error -Message 'Could not determine user name' -ErrorId 'InvalidOperation' -Category InvalidOperation
        return
      }

      # Show interactive menu
      $visibilityManager.ShowUserList()

      Write-Host ""
      Write-Host "Selected user: $targetUserName" -ForegroundColor Cyan
      Write-Host ""

      # Prompt for visibility choice
      Write-Host "Choose visibility option:" -ForegroundColor Yellow
      Write-Host " [1] Hide user from login screen"
      Write-Host " [2] Show user on login screen (Visible)"
      Write-Host " [3] Unset user visibility (restore default system behavior)"
      Write-Host " [4] Exit without changes"

      $choice = Read-Host "Select an option (1-4)"

      switch ($choice) {
        "1" {
          if ($PSCmdlet.ShouldProcess($targetUserName, 'Hide user from logon screen')) {
            $result = $visibilityManager.HideUserAccount($targetUserName, $Force)
            if ($result) {
              Write-Host "User '$targetUserName' has been hidden from the login screen." -ForegroundColor Green
            }
          }
        }
        "2" {
          if ($PSCmdlet.ShouldProcess($targetUserName, 'Show user on logon screen')) {
            $result = $visibilityManager.UnhideUserAccount($targetUserName)
            if ($result) {
              Write-Host "User '$targetUserName' has been set to visible on the login screen." -ForegroundColor Green
            }
          }
        }
        "3" {
          if ($PSCmdlet.ShouldProcess($targetUserName, 'Unset user visibility')) {
            $result = $visibilityManager.ClearUserVisibility($targetUserName)
            if ($result) {
              Write-Host "User '$targetUserName' visibility has been unset (now using default system behavior)." -ForegroundColor Green
            }
          }
        }
        "4" {
          Write-Host "No changes made." -ForegroundColor Gray
        }
        default {
          Write-Error -Message 'Invalid option selected' -ErrorId 'InvalidOperation' -Category InvalidArgument
        }
      }

      return
    }

    $user = $null

    if ($null -ne $InputObject) {
      $user = $InputObject
      $targetName = $user.ToString()
    } elseif ($null -ne $Name) {
      # Check if user exists first
      $user = [LocalAccountHelper]::GetLocalUserByName($Name)
      if ($null -eq $user) {
        $ex = [UserNotFoundException]::new($Name, $Name)
        Write-Error -Message $ex.Message -ErrorId 'UserNotFound' -Category ObjectNotFound -TargetObject $Name
        return
      }
      $targetName = $Name
    } elseif ($null -ne $SID) {
      # Check if user exists first
      $user = [LocalAccountHelper]::GetLocalUserBySid($SID)
      if ($null -eq $user) {
        $ex = [UserNotFoundException]::new($SID.Value, $SID)
        Write-Error -Message $ex.Message -ErrorId 'UserNotFound' -Category ObjectNotFound -TargetObject $SID
        return
      }
      $targetName = $SID.ToString()
    }

    if ($null -eq $user) { return }

    # Handle visibility parameter
    if ($PSBoundParameters.ContainsKey('Visibility')) {
      if ($PSCmdlet.ShouldProcess($targetName, "Set visibility to $Visibility")) {
        $visibilityManager = [SpecialAccountHelper]::new()

        if ($Visibility -eq 'Hidden') {
          $result = $visibilityManager.HideUserAccount($targetName, $Force)
          if (!$result) {
            # Check if already hidden
            $hiddenUsers = [SpecialAccountHelper]::GetHiddenUsersFromRegistry()
            if ($hiddenUsers.ContainsKey($targetName) -and $hiddenUsers[$targetName] -eq 0) {
              Write-Warning "User '$targetName' is already hidden from the logon screen."
            }
          }
        } elseif ($Visibility -eq 'Visible') {
          $result = $visibilityManager.UnhideUserAccount($targetName)
          if (!$result) {
            # Check if already visible
            $hiddenUsers = [SpecialAccountHelper]::GetHiddenUsersFromRegistry()
            if ($hiddenUsers.ContainsKey($targetName) -and $hiddenUsers[$targetName] -eq 1) {
              Write-Warning "User '$targetName' is already set to visible on the logon screen."
            }
          }
        } elseif ($Visibility -eq 'Unset') {
          $result = $visibilityManager.ClearUserVisibility($targetName)
          if (!$result) {
            # Check if already unset
            $hiddenUsers = [SpecialAccountHelper]::GetHiddenUsersFromRegistry()
            if (!$hiddenUsers.ContainsKey($targetName)) {
              Write-Warning "User '$targetName' visibility is already unset (using default system visibility)."
            }
          }
        }
      }
    }

    if ($PSCmdlet.ShouldProcess($targetName, 'Set user account')) {
      try {
        # Check for conflicting parameters
        if ($PSBoundParameters.ContainsKey('AccountExpires') -and $AccountNeverExpires) {
          $ex = [InvalidParametersException]::new('AccountExpires', 'AccountNeverExpires')
          Write-Error -Message $ex.Message -ErrorId 'InvalidParameters' -Category InvalidArgument -TargetObject $targetName
          return
        }

        [LocalAccountHelper]::SetLocalUser(
          $user,
          $Description,
          $FullName,
          $Password,
          $PasswordNeverExpires,
          $UserMayChangePassword,
          $AccountExpires,
          $AccountNeverExpires.IsPresent
        )
      } catch [System.UnauthorizedAccessException] {
        $ex = [AccessDeniedException]::new($targetName)
        Write-Error -Message $ex.Message -ErrorId 'AccessDenied' -Category PermissionDenied -TargetObject $targetName
      } catch {
        Write-Error -Message $_.Exception.Message -ErrorId 'InvalidLocalUserOperation' -Category InvalidOperation -TargetObject $targetName
      }
    }
  }
}