# winrm-client

[![npm version](https://badge.fury.io/js/winrm-client.svg)](https://badge.fury.io/js/winrm-client)
[![Build Status](https://travis-ci.org/shide1989/winrm-client.svg?branch=master)](https://travis-ci.org/shide1989/winrm-client)
[![Build Status](https://dev.azure.com/SHONEJACOB/SHONEJACOB/_apis/build/status/shide1989.winrm-client?branchName=master)](https://dev.azure.com/SHONEJACOB/SHONEJACOB/_build/latest?definitionId=1?branchName=master)

⚠️ This is an updated fork of the original [nodejs-winrm](https://github.com/shoneslab/nodejs-winrm) project that doesn't seem to be maintained anymore.

winrm-client is a production-ready NodeJS client to access WinRM (Windows Remote Management) SOAP web service. It allows to execute commands on target windows machines.
Please visit [Microsoft's WinRM site](http://msdn.microsoft.com/en-us/library/aa384426.aspx) for WINRM details.

#### ⬆️ Migration from nodejs-winrm

Replace `shell` and `command` with `Shell` and `Command`.

```javascript
// CommonJS
const { Shell, Command } = require('winrm-client');
// ES6
import { Shell, Command } from 'winrm-client';
```

## Installation

```bash
# Using npm
npm install winrm-client

# Using pnpm
pnpm add winrm-client

# Using yarn
yarn add winrm-client
```

## Features

- 🔁 Supports both CommonJS and ES6 modules.
- 🏗️ Has types for all exported functions and interfaces.
- 🔐 **Supports both Basic and NTLM authentication** (auto-detected based on username format)
- 🔁 Supports interactive commands that can automatically respond to prompts using three types of detection methods: (see [Interactive Commands](#interactive-commands))
  - Regex Patterns (traditional method)
  - Custom Sync Detectors (new)
  - Custom Async Detectors (new)
- 🔍 Supports debug logging using the `DEBUG` environment variable (see [Debug Logging](#debug-logging))
- 🧪 Supports testing (see [Testing](#testing))

## Supported NodeJS Versions

Supports NodeJS Version >= 16.0.0

Tested on NodeJS versions 16, 18, 20, and latest LTS.

## Supported WinRM Versions

As of now Winrm Version 3 is tested.

```
> winrm id

IdentifyResponse
    ProtocolVersion = http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd
    ProductVendor = Microsoft Corporation
    ProductVersion = OS: 10.0.xxxx SP: 0.0 Stack: 3.0
```

## Remote Installation

On the remote host, a PowerShell prompt, using the **Run as Administrator** option and paste in the following lines:

```
> winrm quickconfig
y
> winrm set winrm/config/service/Auth '@{Basic="true"}'
> winrm set winrm/config/service '@{AllowUnencrypted="true"}'
> winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="1024"}'
```

On the client side where NodeJS is installed

`npm install winrm-client`

## Authentication

winrm-client supports two authentication methods that are **automatically detected** based on the username format:

### Basic Authentication

Used for local usernames (no domain prefix). Requires enabling Basic auth on the WinRM service.

```javascript
// Local username - uses Basic authentication
await runCommand('hostname', 'server', 'Administrator', 'password', 5985);
```

### NTLM Authentication

Used for domain accounts. Automatically enabled when using domain-prefixed usernames.

```javascript
// Domain prefix format (DOMAIN\user) - uses NTLM authentication
await runCommand(
  'hostname',
  'server',
  'DOMAIN\\Administrator',
  'password',
  5985
);

// UPN format (user@domain.com) - uses NTLM authentication
await runCommand('hostname', 'server', 'admin@company.com', 'password', 5985);
```

### Username Format Support

| Username Format | Auth Method | Example           |
| --------------- | ----------- | ----------------- |
| Local           | Basic       | `Administrator`   |
| Domain prefix   | NTLM        | `DOMAIN\user`     |
| UPN             | NTLM        | `user@domain.com` |

### Remote Host Configuration for NTLM

For NTLM authentication, ensure the remote WinRM service allows Negotiate authentication:

```powershell
# Enable Negotiate authentication (includes NTLM)
> winrm set winrm/config/service/Auth '@{Negotiate="true"}'
```

### Development Workflow

For development with TypeScript:

```bash
# Install dependencies
npm install

# Build TypeScript to JavaScript
npm run build

# Watch mode for development
npm run build:watch

# Lint TypeScript files
npm run lint

# Format code with Prettier
npm run format

# Check formatting
npm run format:check
```

### Debug Logging

To enable debug logging, set the `DEBUG` environment variable to `winrm` or `winrm:*`.

Possible values for `namespace` are:

- `*` // Enable debug logging for all namespaces
- `http`
- `shell`
- `command`
- `interactive`
- `runCommand`
- `runPowershell`

To enable debug logging for all namespaces, set the `DEBUG` environment variable to `winrm:*`.

```bash
DEBUG=winrm:* node index.js
```

## Examples

### Run a Single Command

#### JavaScript

```javascript
const winrm = require('winrm-client');
winrm.runCommand(
  'mkdir D:\\winrmtest001',
  '10.xxx.xxx.xxx',
  'username',
  'password',
  5985
);
winrm.runCommand(
  'ipconfig /all',
  '10.xxx.xxx.xxx',
  'username',
  'password',
  5985
);
```

#### TypeScript

```typescript
import { runCommand, runPowershell } from 'winrm-client';

async function executeCommand(): Promise<void> {
  try {
    const result = await runCommand(
      'mkdir D:\\winrmtest001',
      '10.xxx.xxx.xxx',
      'username',
      'password',
      5985
    );
    console.log('Command result:', result);

    const ipResult = await runCommand(
      'ipconfig /all',
      '10.xxx.xxx.xxx',
      'username',
      'password',
      5985
    );
    console.log('IP Config:', ipResult);
  } catch (error) {
    console.error('Error executing command:', error);
  }
}

executeCommand();
```

### Run Command with NTLM Authentication

For domain-joined machines or Azure AD accounts:

```typescript
import { runCommand, runPowershell } from 'winrm-client';

async function executeDomainCommand(): Promise<void> {
  try {
    // Using DOMAIN\user format
    const result = await runCommand(
      'whoami',
      'server.company.com',
      'CORPORATE\\admin',
      'password',
      5985
    );
    console.log('Current user:', result);

    // Using UPN format (user@domain.com)
    const psResult = await runPowershell(
      'Get-Process | Select-Object -First 5',
      'server.company.com',
      'admin@corporate.local',
      'password',
      5985
    );
    console.log('Processes:', psResult);
  } catch (error) {
    console.error('Error:', error);
  }
}

executeDomainCommand();
```

## Interactive Commands

WinRM Client supports interactive commands that can automatically respond to prompts using three types of detection methods:

1. **Regex Patterns** (traditional method)
2. **Custom Sync Detectors** (new)
3. **Custom Async Detectors** (new)

### Basic Interactive Command

```javascript
const winrm = require('winrm-client');

const prompts = [
  {
    pattern: /Enter your name:/i,
    response: 'John Doe',
  },
  {
    pattern: /Password:/i,
    response: 'secret123',
    isSecure: true, // Won't log the response
  },
];

const result = await winrm.runInteractivePowershell(
  'my-interactive-script.ps1',
  'host',
  'username',
  'password',
  5985,
  prompts,
  30000 // timeout in milliseconds
);
```

### Custom Sync Detectors

Use custom synchronous functions for complex prompt detection logic:

```javascript
const prompts = [
  {
    detector: (output) => {
      // Custom logic for detecting prompts
      const lines = output.split('\n');
      return lines.some(
        (line) =>
          line.toLowerCase().includes('password') ||
          line.toLowerCase().includes('passphrase')
      );
    },
    response: 'myPassword123',
    isSecure: true,
  },
  {
    detector: (output) => {
      // Multi-condition detection
      return output.includes('Continue?') && output.includes('(y/n)');
    },
    response: 'y',
  },
];
```

### Custom Async Detectors

Use async functions for detection that requires external API calls, LLM responses, database lookups, or other async operations:

```javascript
const prompts = [
  {
    asyncDetector: async (output) => {
      // Example: External API validation
      const errorCodeMatch = output.match(/Error Code: (\d+)/);
      if (errorCodeMatch) {
        try {
          // Make external API call
          const response = await fetch(
            `https://api.example.com/errors/${errorCodeMatch[1]}`
          );
          const data = await response.json();
          return data.requiresConfirmation ? 'yes' : '';
        } catch {
          return 'no'; // Fallback to regex pattern if available
        }
      }
      return 'no';
    },
    pattern: /Do you want to retry/i, // Fallback pattern
  },
];
```

### Detection Priority and Fallback

The detection methods are prioritized as follows:

1. **Async Detector** (highest priority)
2. **Sync Detector**
3. **Regex Pattern** (fallback)

If a custom detector fails with an error, the system will automatically fall back to the regex pattern if available:

### Error Handling

Custom detectors are wrapped in try-catch blocks to prevent failures from breaking the interactive flow:

- If a custom detector throws an error, it falls back to the regex pattern
- If both custom detector and regex pattern fail, the prompt is skipped
- Errors are logged for debugging purposes

## Testing

`npm test`

### Test Server Setup (Windows)

For e2e testing, use the included setup script on a Windows Server (AWS EC2, etc.):

```bash
# Via SSH
ssh Administrator@<IP> "powershell -ExecutionPolicy Bypass" < scripts/setup-winrm.ps1
```

This configures WinRM + AD DS with a test user. See [`WINRM_SETUP.md`](WINRM_SETUP.md) for details.

## Maintainers

- Sebastien Hideux (https://github.com/shide1989)
