---
title: Create or update an external user
summary: Persists EXTERNAL-domain account attributes, the test-user flag, and an optional password change for a new or existing Active Directory account.
status: draft
owner: SAIF
last_reviewed: 2026-07-30
library: SAIF.Business.Security.SystemUser
reference_type: package
operation_id: OP-SU-04
trigger: Caller calls clsSystemUser.AddNewUser(...) to create a new account, or UpdateUser(...) to modify an existing account
---

# Create or update an external user

`AddNewUser` and `UpdateUser` are the library's two account-CRUD writers. Both map the same `UserStructure` fields onto a `clsUser` directory entry, both derive the AD `Description` attribute from the test-user flag, and both optionally push a new password through `DirectoryEntry.Invoke("SetPassword", ...)`. They are documented together because they share this field-mapping and test-user-flag logic and are governed by the same rule; `AddNewUser` additionally constructs a brand-new EXTERNAL-domain directory entry first, and `UpdateUser` additionally locates the existing entry first and only changes the password when one was supplied.

| Field | Value |
| --- | --- |
| **Operation ID** | OP-SU-04 |
| **Trigger** | `AddNewUser(inUserStructure, inCreatedBy)` for a new account; `UpdateUser(inUserStructure, inUpdatedBy)` for an existing account |
| **Inputs** | `UserStructure` (user ID, password, ID number, first/last name, company, job title, phone, fax, email, address, test-user flag); actor ID (`inCreatedBy` / `inUpdatedBy`) |
| **Outputs** | `Boolean` result; created or updated AD directory entry with mapped attributes and optional new password |
| **Primary components** | `clsSystemUser.AddNewUser(...)`, `clsSystemUser.UpdateUser(...)`, `clsSystemUser.LoadThisUser(...)` |
| **Entry point** | `Business\SAIF.Business.Security.SystemUser\clsSystemUser.vb` |

## Steps

1. **Create — construct a new EXTERNAL directory entry.** `AddNewUser` instantiates `New SAIF.DirectoryServices.clsUser("EXTERNAL")` and immediately replaces it with the factory-produced object from `clsUser.FactoryGetObject(m_DirUser.m_DirEntry)`.
2. **Update — locate the existing entry first.**
   - [Switch active user context](switch-active-user-context.md) is invoked via `LoadThisUser(inUserStructure.userID)` to resolve and bind `m_DirUser` to the account being updated.
   - If the user is not found: `UpdateUser` returns `False` immediately and performs no field mapping.
3. **Map the shared field set onto `m_DirUser`.** Both methods copy the same fields from `UserStructure` onto the directory entry: `MailID`, `IDNumber`, `FirstName`, the `sn` (last name) property directly on `m_DirEntry`, `Title`, `Company`, `Email`, `PhoneNumber`, `FaxNumber`, and `StreetAddress`.
4. **Record the actor.**
   - `AddNewUser` sets `Division` to `inCreatedBy`.
   - `UpdateUser` sets `Comment` to `inUpdatedBy`.
5. **Set the test-user flag.** Both methods set `Description` to `"Y"` when `UserStructure.isTestUser` is true, or `"N"` otherwise, so the flag is recoverable later through the `TestUser` property ([BR-012](../business-rules.md#br-012-test-user-flags-are-stored-in-the-ad-description-attribute)).
6. **Apply the password.**
   - `AddNewUser` always invokes `DirectoryEntry.Invoke("SetPassword", .password)` and commits, unconditionally setting the password on the new account.
   - `UpdateUser` only invokes `SetPassword` and commits `.password.Length > 0` — an empty password on the input structure leaves the existing AD password untouched.
7. **Commit the changes.** Both methods call `m_DirUser.m_DirEntry.CommitChanges()` after setting the password (inside the same conditional block for `UpdateUser`).
8. **Return the outcome.**
   - `AddNewUser` returns the local `isOkay` variable, which is declared but never assigned a value anywhere in the method body — it is always `False` on the success path, regardless of whether the directory writes succeeded.*
   - `UpdateUser` returns the `isOkay` value produced by the `LoadThisUser` call in step 2 — `True` when the user was found and the subsequent field mapping executed, `False` only when the user lookup failed.

## Process flow

The following diagram visualizes this operation's control flow across both entry points.

```mermaid
flowchart TD
	Start([Caller invokes AddNewUser or UpdateUser]) --> Which{Which entry point?}
	Which -->|AddNewUser| NewEntry[Construct new EXTERNAL clsUser via factory]
	Which -->|UpdateUser| Load[LoadThisUser by UserStructure.userID]
	Load --> Found{User found?}
	Found -->|No| Fail([Return False; no mapping applied])
	Found -->|Yes| Map
	NewEntry --> Map[Map shared UserStructure fields onto m_DirUser]
	Map --> Actor[Record actor: Division or Comment]
	Actor --> TestFlag[Set Description from isTestUser flag]
	TestFlag --> PwdCheck{Password supplied?}
	PwdCheck -->|AddNewUser: always| SetPwd[Invoke SetPassword]
	PwdCheck -->|UpdateUser: only if length > 0| SetPwd
	PwdCheck -->|UpdateUser: empty password| Commit
	SetPwd --> Commit[CommitChanges]
	Commit --> ReturnAdd{Which entry point?}
	ReturnAdd -->|AddNewUser| RetFalse([Return isOkay - always False*])
	ReturnAdd -->|UpdateUser| RetTrue([Return isOkay - True])
```

## Failure and edge-case behavior

- `AddNewUser` always returns `False` because its `isOkay` result variable is never assigned on any path; callers relying on the return value cannot distinguish a successful account creation from a failed one through this method's result alone.*
- `AddNewUser` performs no existence check before creating a new directory entry, so calling it with a `UserID` that already exists depends entirely on how the underlying AD write and factory call behave for a duplicate — no explicit duplicate-detection logic exists in this method.*
- `UpdateUser`'s password branch means submitting an update with an empty `password` field is the supported way to leave the AD password unchanged; there is no separate "don't touch the password" flag.
- Neither method validates the mapped field values (e.g., email format, phone format) before writing them to Active Directory.

## Related documentation

- [Operations and processes](../operations-processes.md)
- [Business rules](../business-rules.md)

> **Note:** Statements marked with `*` were inferred from code structure, naming
> conventions, or contextual evidence where direct confirmation was not available
> in source artifacts.

## Sources

- `Business\SAIF.Business.Security.SystemUser\clsSystemUser.vb`
