> For the complete documentation index, see [llms.txt](https://docs.openiam.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.openiam.com/connector-configuration/microsoft/10-winlocal.md).

# WinLocal OpenIAM connector

This page describes how to work with standalone Windows Server users and groups

The WinLocal connector manages **local users and groups on Windows Servers that are not domain-joined** — the accounts held in the machine's own Security Account Manager rather than in a directory. It reaches each target machine over **WinRM** and runs the local-account cmdlets there, so one connector serves many machines.

The connector is a .NET/PowerShell connector: it runs as a Windows service on a host that can reach the target machines over WinRM, and exchanges requests with OpenIAM over RabbitMQ.

| Operation                | Users                                           | Groups                                       |
| ------------------------ | ----------------------------------------------- | -------------------------------------------- |
| Save — create or update  | ✅                                               | ✅                                            |
| Delete                   | ✅                                               | ✅                                            |
| Search                   | ✅                                               | ✅                                            |
| Suspend / resume         | ✅ Disables or enables the account               | ❌ Refused — a local group cannot be disabled |
| Reset password           | ✅                                               | ❌ Refused                                    |
| Check a password (Login) | ❌ Refused — the connector has no implementation | ❌ Refused                                    |
| Test connection          | ✅ Opens a real WinRM session to the target      |                                              |

There is no separate create operation. OpenIAM sends a save, and the connector looks the identity up first, creating the account only when it does not find one.

A delete of an account or group that is no longer there **reports success** — the connector looks it up, finds nothing and says so in the log rather than failing the request.

## Use cases

* **Standalone and workgroup servers** — DMZ hosts, appliances and lab machines that never joined the domain, but whose local accounts still have to be provisioned and de-provisioned like any other.
* **Local administrator accounts** — creating them from OpenIAM, putting them in the right local groups, and removing them when the person leaves, instead of by hand on each machine.
* **Reconciling what is actually on the machine** — reading back local users, local groups and their membership, including which members are local, from the domain, or from Entra ID.

## Versions and compatibility

The end application here is **Windows itself** — the local accounts held in a machine's own Security Account Manager. What decides whether a target machine works is the PowerShell available on it, because the connector runs the [Microsoft.PowerShell.LocalAccounts](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.localaccounts/) cmdlets there rather than calling an API.

| Operation                            | Supported                                                                                                                                                                                                |
| ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Target machines**                  | Windows with **PowerShell 5.1** and WinRM reachable. That is where the LocalAccounts cmdlets come from, and it is the real requirement — not the Windows release on its own                              |
| **Windows Server 2016 and newer**    | Fully supported                                                                                                                                                                                          |
| **Windows Server 2012 R2 and older** | Works, with one gap in what a search returns — see below                                                                                                                                                 |
| **Connector host**                   | Windows Server 2016 or newer, as for every .NET/PowerShell connector. See [Installing PowerShell connectors](/connector-configuration/microsoft/1-powershellconnectorinstallation.md#basic-requirements) |
| **Domain membership**                | Irrelevant. The connector exists for machines that are **not** domain-joined, and works the same either way                                                                                              |

The connector has no version range of its own. Its attribute names are the LocalAccounts cmdlets' own parameter names, which is what [Attribute mapping and policy map](#attribute-mapping-and-policy-map) documents.

{% hint style="warning" %}
**On Windows Server 2012 R2 and older, three search attributes are never produced**: `LocalGroups`, `AzuerAdUsers` and `AzuerAdGroups`. `Get-LocalGroupMember` does not report a member's origin on those versions, so the connector reads origin from the prefix on the member's name. Entra ID join is newer than those releases, so no member carries that origin at all, and a local group nested inside another group is reported under `OtherGroups` instead. A query that selects one of those three attributes on such a host leaves it empty.
{% endhint %}

{% hint style="info" %}
An earlier **version 4** was built on ADSI rather than the LocalAccounts module. It is no longer shipped and is no longer documented. Its attribute names differ and version 5 does not accept them, so a managed system carried over from it has to be remapped.
{% endhint %}

## Prerequisites

The operating system, `.NET Framework` and PowerShell requirements for the connector host are the same for every .NET/PowerShell connector and are listed once in [Installing PowerShell connectors](/connector-configuration/microsoft/1-powershellconnectorinstallation.md#basic-requirements). Beyond those:

* **WinRM enabled between the connector host and every target machine.** This is the connector's only transport; nothing works without it.
* **A service account with sufficient rights on the targets.** The connector performs every operation as the account you configure.

{% hint style="info" %}
Give the service account **machine-qualified**, as `MachineName\serviceAccount`. A bare `Administrator` is read as a domain name and authentication fails — see [Troubleshooting](#troubleshooting).
{% endhint %}

## Installing the connector

Every .NET/PowerShell connector installs the same way. See [Installing PowerShell connectors](/connector-configuration/microsoft/1-powershellconnectorinstallation.md) for the installer, running several connectors on one host, and removing or updating an installation.

## Starting, stopping, and restarting

The installer registers a pair of Windows services named after the connector name you supply. See [Installing PowerShell connectors](/connector-configuration/microsoft/1-powershellconnectorinstallation.md#starting-stopping-and-restarting).

## Command-line parameters and properties

The connector takes no command-line parameters. Queue names, concurrency, logging, simulation mode and the RabbitMQ settings live in `Connector.config`, documented for the whole family in [Using PowerShell connectors](/connector-configuration/microsoft/2-powershellconnectorsusage.md#working-with-the-connector-configuration).

## Verifying the connector is running

{% stepper %}
{% step %}
Confirm the connector's Windows services are running on the host.
{% endstep %}

{% step %}
Check that the connector's request queue in RabbitMQ has a consumer.
{% endstep %}

{% step %}
Run **Test Connection** on the managed system. It opens a real WinRM session to the target and runs a command there, so a pass means the transport, the service account and the target are all good.
{% endstep %}

{% step %}
Run a search to prove the path end to end.
{% endstep %}
{% endstepper %}

## Troubleshooting

Troubleshooting for the whole family, including where the logs are and how to replay a request, is in [Using PowerShell connectors](/connector-configuration/microsoft/2-powershellconnectorsusage.md). What follows is specific to WinLocal, and both failures below are WinRM rather than the connector.

### Reproducing a read by hand

When data will not come back from a target, it is often quickest to make the same call yourself, the way the connector does. This pulls the local groups from a remote server:

{% code overflow="wrap" %}

```powershell
$computername = 'srvName'
$credentials = Get-Credential

$remoteResult = Invoke-Command -ComputerName $computername -Credential $credentials -ScriptBlock {
    $computername = $env:COMPUTERNAME
    $adsi = [ADSI]"WinNT://$computername"
    $adsi.Children | Where-Object {$_.SchemaClassName -eq 'group'} | Select-Object *
}

$remoteResult
```

{% endcode %}

### WinRM is not enabled on the target

{% code overflow="wrap" %}

```
Connecting to remote server failed with the following error message : WinRM cannot complete the
operation. Verify that the specified computer name is valid, that the computer is accessible over
the network, and that a firewall exception for the WinRM service is enabled and allows access from
this computer.
FullyQualifiedErrorId : WinRMOperationTimeout,PSSessionStateBroken
```

{% endcode %}

Enable WinRM on the target machine:

```powershell
Enable-PSRemoting -Force
```

Note that by default the WinRM firewall exception for public profiles only allows access from machines on the same subnet.

### The service account is in the wrong format

{% code overflow="wrap" %}

```
Connecting to remote server failed with the following error message : WinRM cannot process the
request. The following error with errorcode 0x8009030d occurred while using Negotiate
authentication: A specified logon session does not exist. It may already have been terminated.
FullyQualifiedErrorId : 1312,PSSessionStateBroken
```

{% endcode %}

The usual cause is a service account given without its machine prefix. Kerberos accepts domain user names but not bare local ones, so `Administrator` fails where `somePC\Administrator` succeeds. The other causes the message lists — a wrong password, no trust between domains, a missing SPN — are worth checking only once the format is right.

## Registering the connector in OpenIAM

The connector's request queue name in OpenIAM has to match the queue in the connector's own `Connector.config` exactly. Getting the connection parameters out of OpenIAM and into the installer is covered in [Installing PowerShell connectors](/connector-configuration/microsoft/1-powershellconnectorinstallation.md#getting-connection-parameters-with-openiam).

## Configuring the managed system

Follow the standard steps in [Configure a managed system](/administration-guide/lifecycle/configure-managed-system.md). One managed system addresses one target machine, and these fields are what the connector builds its WinRM call from:

| Field        | Value                                                                                                                                                                                          |
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **URL**      | The target machine's host name or IP address                                                                                                                                                   |
| **Port**     | Optional. A TCP port between 1 and 65535 — **leave it empty to use the default WinRM port**. A value that is not a valid port number fails the request rather than falling back to the default |
| **Login Id** | The service account, **machine-qualified** as `MachineName\serviceAccount`                                                                                                                     |
| **Password** | That account's password                                                                                                                                                                        |

<figure><img src="https://2965242200-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0uHWHRgcad6P03ehqgmX%2Fuploads%2Fgit-blob-32a1dca9795b6313db8cad09f835a6c7f5025e7a%2F10-winlocal-01-managed-system.png?alt=media" alt="The managed system form for the WinLocal PowerShell Connector, with the target machine name in Host URL, the Port field left empty, and a machine-qualified service account in Login Id"><figcaption></figcaption></figure>

{% hint style="warning" %}
**A managed system that has to create or rename accounts must use `Name` as its principal attribute.** Windows assigns the SID when an account is created, so one that does not exist yet cannot be addressed by SID — and a rename replaces the very value the object is addressed by, so it only works when that value is the name. A SID-keyed managed system can look accounts up, update and delete them, but every create and every rename is refused. It is not set on the form above — it is the row marked as the identity on the [policy map](/administration-guide/lifecycle/policy-map.md).
{% endhint %}

### Connecting over SSL

To reach targets over HTTPS rather than HTTP, **WinRM must first be configured for HTTPS on the target machines**. Microsoft's [How to configure WinRM for HTTPS](https://learn.microsoft.com/en-us/troubleshoot/windows-client/system-management-components/configure-winrm-for-https) covers that.

Then set the **`useSSL`** attribute to **true** on the managed system:

<figure><img src="https://2965242200-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0uHWHRgcad6P03ehqgmX%2Fuploads%2Fgit-blob-a9caeb32098018aee2b6218ecd492529eb169240%2FuseSSL-attribute.png?alt=media" alt="The Attributes panel of a managed system with a single attribute named useSSL, its value ticked"><figcaption></figcaption></figure>

### Custom handlers

WinLocal accepts the widest set of handler overrides in the family. A managed system attribute whose value is a script filename replaces the built-in operation with that script, which must live in the connector's installation folder.

| Attribute                              | Replaces                                                  |
| -------------------------------------- | --------------------------------------------------------- |
| `ADD_HANDLER`                          | The create half of save, chosen after the identity lookup |
| `MODIFY_HANDLER`                       | The update half of save                                   |
| `DELETE_HANDLER`                       | Delete                                                    |
| `PASSWORD_HANDLER`                     | Reset password and login                                  |
| `SUSPEND_HANDLER` and `RESUME_HANDLER` | Suspend and resume                                        |
| `SEARCH_HANDLER`                       | Search                                                    |
| `TEST_CONNECTION_HANDLER`              | Test connection                                           |

A handler takes over that operation entirely — which is also the only way to make login do anything, since the connector's own version refuses it.

## Attribute mapping and policy map

Attributes reach the target machine through the policy map on the managed system — see [Configure a managed system](/administration-guide/lifecycle/configure-managed-system.md).

**Every value carries an operation code**, and the connector acts on the code rather than only on the value:

| Code        | Meaning                                                                         |
| ----------- | ------------------------------------------------------------------------------- |
| `ADD`       | Set the value, or add it to a multi-valued attribute                            |
| `REPLACE`   | Set the value, replacing what is there. For most attributes it behaves as `ADD` |
| `DELETE`    | Clear the attribute, or leave the group                                         |
| `NO_CHANGE` | Skip the attribute entirely                                                     |

This is why one map covers both directions: a user joins and leaves a local group through the same `MemberOf` attribute, and a group gains and loses members through the same `Members`. Only a few attributes accept `DELETE` at all, and each version page lists which.

**Renaming does not travel through the map.** OpenIAM sends a rename as the previous name in `originalIdentityValue`; a name in the policy map that disagrees with the identity value is refused before anything runs.

Save is split in two. The connector looks the identity up first and then takes either the create or the update path, and the two accept different attributes — which is also why they can be routed to different scripts, through `ADD_HANDLER` and `MODIFY_HANDLER`.

### The accepted names are resolved at run time

The connector holds no fixed list of attributes. On each operation it asks PowerShell what the target cmdlet accepts and passes through whatever matches:

| Operation        | Names accepted are those of |
| ---------------- | --------------------------- |
| Creating a user  | `New-LocalUser`             |
| Updating a user  | `Set-LocalUser`             |
| Creating a group | `New-LocalGroup`            |
| Updating a group | `Set-LocalGroup`            |

The tables below are the attributes deployments use most, not the full set — anything else the cmdlet accepts works too, and an attribute it does not recognize is dropped rather than rejected, so a typo in the policy map fails quietly. `MemberOf` and `Members` are the exception: they are the connector's own, handled apart from the cmdlet.

{% hint style="warning" %}
**The wildcard characters `*`, `?`, `[` and `]` are refused in any name.** That covers the identity value and every group or principal name inside `Members` and `MemberOf`, whatever operation code it carries — a `DELETE` value is checked exactly like an `ADD` one. A local account lookup would expand a wildcard into a pattern that matches several accounts at once, so the request is refused before it reaches the target rather than acting on whatever the pattern happened to match. They are not valid in a Windows local account or group name either.
{% endhint %}

{% hint style="warning" %}
**Creating or renaming anything requires the managed system's principal attribute to be `Name`.** Windows assigns the SID when the account is created, so an account that does not exist yet cannot be addressed by one — and a rename replaces the value the object is addressed by, which only works when that value is the name. A managed system keyed on `SID` can look accounts up, update and delete them; creates and renames are refused outright, each with a message saying so.
{% endhint %}

### Creating a user

The name comes from the **identity value**, not from a mapped `Name` attribute — whatever the policy map supplies for `Name` is overwritten by it.

A new account needs either a `Password` or `NoPassword`. `NoPassword` has to carry exactly `true`; a mapping that sends `NULL`, an empty value or anything else unrecognized is refused with a message naming what it received, rather than creating an account with no password by accident.

| Attribute name             | Description                                                                                                                                                                                            | Type     |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------- |
| `Name`                     | The user name, taken from the identity value. Up to 20 characters. It cannot contain `\ / [ ] " &#124; < > + = ; , ? * @` and cannot consist only of periods or spaces                                 | string   |
| `Password`                 | The password for the account. **Required unless `NoPassword` is used**                                                                                                                                 | string   |
| `NoPassword`               | Creates the account with no password. **Required if `Password` is omitted**, and must be exactly `true`. Cannot be combined with `Password` or `PasswordNeverExpires`                                  | bool     |
| `PasswordNeverExpires`     | Whether the password expires                                                                                                                                                                           | bool     |
| `UserMayNotChangePassword` | Prevents the user changing their own password                                                                                                                                                          | bool     |
| `AccountExpires`           | When the account expires. Cannot be combined with `AccountNeverExpires`                                                                                                                                | datetime |
| `AccountNeverExpires`      | The account does not expire. Cannot be combined with `AccountExpires`                                                                                                                                  | bool     |
| `Disabled`                 | Creates the account disabled                                                                                                                                                                           | bool     |
| `Enabled`                  | Handled by the connector rather than passed to the cmdlet: a value that does not read as true creates the account **disabled**, so there is no window in which it exists enabled with a valid password | bool     |
| `FullName`                 | The account's full name, which differs from its user name                                                                                                                                              | string   |
| `Description`              | A comment on the account. Up to 48 characters                                                                                                                                                          | string   |
| `MemberOf`                 | *Connector attribute.* An array of local group names to join. Only `ADD` and `REPLACE` are acted on at create time                                                                                     | array    |

### Updating a user

Three attributes are **ignored on update**, because each has its own path:

| Attribute  | Why, and what to use instead                                                                                                                                                                                                                                                                                                                                          |
| ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Name`     | Renaming is not done by mapping a new name. OpenIAM sends a rename as the previous value in `originalIdentityValue`, and a `Name` in the policy map that disagrees with the identity value is **refused** before anything runs. The rename is applied last, after the other attributes, and it is case-sensitive — changing only the case is a real rename in Windows |
| `Password` | Use the reset password operation                                                                                                                                                                                                                                                                                                                                      |
| `SID`      | Assigned by Windows and never writable                                                                                                                                                                                                                                                                                                                                |

| Attribute name          | Description                                                                                                  | Type     |
| ----------------------- | ------------------------------------------------------------------------------------------------------------ | -------- |
| `AccountExpires`        | When the account expires                                                                                     | datetime |
| `AccountNeverExpires`   | The account does not expire                                                                                  | bool     |
| `Description`           | A comment on the account. Up to 48 characters                                                                | string   |
| `FullName`              | The account's full name                                                                                      | string   |
| `PasswordNeverExpires`  | Whether the password expires                                                                                 | bool     |
| `UserMayChangePassword` | Whether the user can change their own password                                                               | bool     |
| `Enabled`               | *Connector attribute.* Enables or disables the account. Anything that does not read as true disables it      | bool     |
| `MemberOf`              | *Connector attribute.* An array of local group names. `ADD` and `REPLACE` join the group, `DELETE` leaves it | array    |

**Clearing a value** — sending an attribute with the `DELETE` operation — works for four attributes only:

| Attribute        | Effect of `DELETE`         |
| ---------------- | -------------------------- |
| `AccountExpires` | Sets `AccountNeverExpires` |
| `Description`    | Clears it                  |
| `FullName`       | Clears it                  |
| `MemberOf`       | Leaves the named groups    |

`DELETE` on any other attribute fails the request with *"Not supported operation 'Delete' for attribute \<name>"* rather than being ignored.

### Resetting a password

`Password` is required and **cannot be empty** — an empty value is refused rather than setting a blank password. Two other attributes can be set in the same operation:

| Attribute name          | Description                                           | Type   |
| ----------------------- | ----------------------------------------------------- | ------ |
| `Password`              | The new password. **Required**, and must not be empty | string |
| `PasswordNeverExpires`  | Whether the new password expires                      | bool   |
| `UserMayChangePassword` | Whether the user can change it                        | bool   |

Reset password is refused for groups.

### Creating a group

The group name comes from the identity value, exactly as it does for a user.

| Attribute name | Description                                                                                                                    | Type   |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------ | ------ |
| `Name`         | The group name, taken from the identity value. Up to 256 characters                                                            | string |
| `Description`  | A comment on the group. Up to 48 characters                                                                                    | string |
| `Members`      | *Connector attribute.* An array of users or groups, given by name or SID. Only `ADD` and `REPLACE` are acted on at create time | array  |

### Updating a group

`Name` and `SID` are ignored here for the same reasons as on a user: renaming travels through `originalIdentityValue`, and the SID is not writable.

| Attribute name | Description                                                                                                         | Type   |
| -------------- | ------------------------------------------------------------------------------------------------------------------- | ------ |
| `Description`  | A comment on the group. Up to 48 characters                                                                         | string |
| `Members`      | *Connector attribute.* An array of users or groups, given by name or SID. `ADD` and `REPLACE` add, `DELETE` removes | array  |

`DELETE` works for `Description` and `Members` only; any other attribute fails the request. Clearing a description is the one operation `Set-LocalGroup` cannot perform, so the connector falls back to ADSI for it — which is why a group description clears even though the cmdlet has no way to do it.

## Reconciliation and synchronization

How synchronization and reconciliation work in general is covered in the Administration guide — see [*Synchronize and import data*](/administration-guide/lifecycle/synchronize-and-import-data.md) and [*Configure reconciliation*](/administration-guide/lifecycle/configure-reconciliation.md).

Set the search string in the ***SQL Query / Directory Filter*** field on the synchronization page. The connector runs it as a PowerShell expression on the target machine and checks it first: it has to be **one pipeline that only reads**. Anything that changes state is refused, and for WinLocal `net`, `net.exe`, `wmic` and `wmic.exe` are refused by name as well, because they can write while looking like a read.

The connector reads with **`Get-LocalUser`**, **`Get-LocalGroup`** and **`Get-LocalGroupMember`**, and a search returns whichever of those cmdlets' properties you name in the managed system's search attributes. The full list of what each one produces is Microsoft's, in the [Microsoft.PowerShell.LocalAccounts reference](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.localaccounts/), and it moves with the module rather than with the connector. `Name` and `SID` are on all three.

Two things in a search result are the connector's own rather than the cmdlets'. **`Members`** on a group carries the names of the users and groups in it. And membership is split by where each member came from, so that OpenIAM can tell them apart:

| Attribute name | Description                | Type      |
| -------------- | -------------------------- | --------- |
| LocalUsers     | Local users' names         | string\[] |
| DomainUsers    | Domain users' names        | string\[] |
| AzuerAdUsers   | Users' names from Entra ID | string\[] |
| OtherUsers     | Other users' names         | string\[] |
| LocalGroups    | Local groups               | string\[] |
| DomainGroups   | Domain groups              | string\[] |
| AzuerAdGroups  | Groups from Entra ID       | string\[] |
| OtherGroups    | Other groups               | string\[] |

{% hint style="warning" %}
**`AzuerAdUsers` and `AzuerAdGroups` are spelled exactly as shown.** The misspelling is in the connector itself, not on this page, and the attribute names it accepts are the misspelled ones. Writing `AzureAdUsers` in a managed system or a search query silently yields nothing.
{% endhint %}

Three of these attributes are not produced on older Windows — see the note under [Versions and compatibility](#versions-and-compatibility).


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.openiam.com/connector-configuration/microsoft/10-winlocal.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
