Skip to content

Commit

Permalink
First version
Browse files Browse the repository at this point in the history
  • Loading branch information
fakoua committed Jan 5, 2024
1 parent 41e9186 commit f9d4387
Show file tree
Hide file tree
Showing 9 changed files with 482 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
playground.ts
22 changes: 22 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"request": "launch",
"name": "Launch Program",
"type": "node",
"program": "${workspaceFolder}/playground.ts",
"cwd": "${workspaceFolder}",
"runtimeExecutable": "C:\\Users\\Sam\\.deno\\bin\\deno.EXE",
"runtimeArgs": [
"run",
"--inspect-wait",
"--allow-all"
],
"attachSimplePort": 9229
}
]
}
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"deno.enable": true,
"deno.lint": true,
"deno.unstable": false
}
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,22 @@
# deno_winrm
deno_winrm is a Deno typescript client for the Windows Remote Management (WinRM) service. It allows you to invoke commands on target Windows machines using Deno.

## Examples:

```ts
import * as winrm from "https://deno.land/x/deno_winrm/mod.ts";

const context = new winrm.WinRMContext({
username: "my_user",
password: "my_password",
}, "machine_name_or_ip");

const result = await context.runCommand("ipconfig /all");

if (result.success) {
console.log(result.message);
} else {
console.log(result.error?.message);
}

```
1 change: 1 addition & 0 deletions mod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {WinRMContext} from "./src/winrm_context.ts"
35 changes: 35 additions & 0 deletions src/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
export const DEFAULT_PORT = 5985;

export type CommandResponse = {
success: boolean,
message: string,
error?: {
reason?: string,
message?: string
}
}

export type BasicAuthentication = {
username: string,
password: string,
}

export type WinRMHost = {
hostname: string,
port: 5985,
protocol: 'http',
}

export type HttpResponse = {
status: number,
statusText: string,
body: string,
}

export function constractHost(host: WinRMHost | string): string {
const isStringHost = typeof host === "string";
const protocol = isStringHost ? "http" : host.protocol;
const hostname = isStringHost ? host : host.hostname;
const port = isStringHost ? DEFAULT_PORT : host.port;
return `${protocol}://${hostname}:${port}/wsman`;
}
53 changes: 53 additions & 0 deletions src/soap_client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { encodeBase64 } from "https://deno.land/std@0.210.0/encoding/base64.ts";
import {
BasicAuthentication,
constractHost,
HttpResponse,
WinRMHost,
} from "./common.ts";

export class SoapClient {
private winrmServer: string;

constructor(
private auth: BasicAuthentication,
private host: WinRMHost | string,
) {
this.winrmServer = constractHost(this.host);
}

async httpRequest(soap: string): Promise<HttpResponse> {
const auth = this.getBasicAuth(this.auth.username, this.auth.password);

try {
const respose = await fetch(this.winrmServer, {
method: "POST",
cache: "no-cache",
headers: {
"Authorization": auth,
"Content-Type": "application/soap+xml;charset=UTF-8",
"User-Agent": "Deno WinRM Client",
"Content-Length": `${soap.length}`,
},
body: soap,
});

const body = await respose.text();
return {
body: body,
status: respose.status,
statusText: respose.statusText,
};
} catch (error) {
return {
body: error.toString(),
status: 0,
statusText: "",
};
}
}

private getBasicAuth(username: string, password: string): string {
return `Basic ${encodeBase64(username + ":" + password)}`;
}
}
124 changes: 124 additions & 0 deletions src/soap_messages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
export function message_shellid(server: string, messageId: string): string {
return `<?xml version="1.0" encoding="UTF-8"?>
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:p="http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd" xmlns:rsp="http://schemas.microsoft.com/wbem/wsman/1/windows/shell" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:wsman="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd">
<s:Header>
<wsa:To>${server}</wsa:To>
<wsman:ResourceURI mustUnderstand="true">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd</wsman:ResourceURI>
<wsa:ReplyTo>
<wsa:Address mustUnderstand="true">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsa:Address>
</wsa:ReplyTo>
<wsman:MaxEnvelopeSize mustUnderstand="true">153600</wsman:MaxEnvelopeSize>
<wsa:MessageID>uuid:${messageId}</wsa:MessageID>
<wsman:Locale mustUnderstand="false" xml:lang="en-US" />
<wsman:OperationTimeout>PT60S</wsman:OperationTimeout>
<wsa:Action mustUnderstand="true">http://schemas.xmlsoap.org/ws/2004/09/transfer/Create</wsa:Action>
<wsman:OptionSet>
<wsman:Option Name="WINRS_NOPROFILE">FALSE</wsman:Option>
<wsman:Option Name="WINRS_CODEPAGE">437</wsman:Option>
</wsman:OptionSet>
</s:Header>
<s:Body>
<rsp:Shell>
<rsp:InputStreams>stdin</rsp:InputStreams>
<rsp:OutputStreams>stderr stdout</rsp:OutputStreams>
</rsp:Shell>
</s:Body>
</s:Envelope>`;
}

export function message_commandid(
server: string,
messageId: string,
shellId: string,
command: string,
): string {
return `<?xml version="1.0" encoding="UTF-8"?>
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:p="http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd" xmlns:rsp="http://schemas.microsoft.com/wbem/wsman/1/windows/shell" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:wsman="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd">
<s:Header>
<wsa:To>${server}</wsa:To>
<wsman:ResourceURI mustUnderstand="true">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd</wsman:ResourceURI>
<wsa:ReplyTo>
<wsa:Address mustUnderstand="true">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsa:Address>
</wsa:ReplyTo>
<wsman:MaxEnvelopeSize mustUnderstand="true">153600</wsman:MaxEnvelopeSize>
<wsa:MessageID>uuid:${messageId}</wsa:MessageID>
<wsman:Locale mustUnderstand="false" xml:lang="en-US" />
<wsman:OperationTimeout>PT60S</wsman:OperationTimeout>
<wsa:Action mustUnderstand="true">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Command</wsa:Action>
<wsman:SelectorSet>
<wsman:Selector Name="ShellId">${shellId}</wsman:Selector>
</wsman:SelectorSet>
<wsman:OptionSet>
<wsman:Option Name="WINRS_CONSOLEMODE_STDIN">TRUE</wsman:Option>
<wsman:Option Name="WINRS_SKIP_CMD_SHELL">FALSE</wsman:Option>
</wsman:OptionSet>
</s:Header>
<s:Body>
<rsp:CommandLine>
<rsp:Command>${command}</rsp:Command>
</rsp:CommandLine>
</s:Body>
</s:Envelope>`;
}

export function message_command(
server: string,
messageId: string,
shellId: string,
commandId: string,
): string {
return `<?xml version="1.0" encoding="UTF-8"?>
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:p="http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd" xmlns:rsp="http://schemas.microsoft.com/wbem/wsman/1/windows/shell" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:wsman="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd">
<s:Header>
<wsa:To>${server}</wsa:To>
<wsman:ResourceURI mustUnderstand="true">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd</wsman:ResourceURI>
<wsa:ReplyTo>
<wsa:Address mustUnderstand="true">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsa:Address>
</wsa:ReplyTo>
<wsman:MaxEnvelopeSize mustUnderstand="true">153600</wsman:MaxEnvelopeSize>
<wsa:MessageID>uuid:${messageId}</wsa:MessageID>
<wsman:Locale mustUnderstand="false" xml:lang="en-US" />
<wsman:OperationTimeout>PT60S</wsman:OperationTimeout>
<wsa:Action mustUnderstand="true">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Receive</wsa:Action>
<wsman:SelectorSet>
<wsman:Selector Name="ShellId">${shellId}</wsman:Selector>
</wsman:SelectorSet>
</s:Header>
<s:Body>
<rsp:Receive>
<rsp:DesiredStream CommandId="${commandId}">stdout stderr</rsp:DesiredStream>
</rsp:Receive>
</s:Body>
</s:Envelope>`;
}

export function message_delete_shellid(
server: string,
messageId: string,
shellId: string,
) {
return `<?xml version='1.0'?>
<s:Envelope
xmlns:s='http://www.w3.org/2003/05/soap-envelope'
xmlns:wsa='http://schemas.xmlsoap.org/ws/2004/08/addressing'
xmlns:wsman='http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd'
xmlns:p='http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd'
xmlns:rsp='http://schemas.microsoft.com/wbem/wsman/1/windows/shell'>
<s:Header>
<wsa:To>${server}</wsa:To>
<wsman:ResourceURI mustUnderstand='true'>http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd</wsman:ResourceURI>
<wsa:ReplyTo>
<wsa:Address mustUnderstand='true'>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsa:Address>
</wsa:ReplyTo>
<wsman:MaxEnvelopeSize mustUnderstand='true'>153600</wsman:MaxEnvelopeSize>
<wsa:MessageID>uuid:${messageId}</wsa:MessageID>
<wsman:Locale mustUnderstand='false' xml:lang='en-US'/>
<wsman:OperationTimeout>PT60S</wsman:OperationTimeout>
<wsa:Action mustUnderstand='true'>http://schemas.xmlsoap.org/ws/2004/09/transfer/Delete</wsa:Action>
<wsman:SelectorSet>
<wsman:Selector Name='ShellId'>${shellId}</wsman:Selector>
</wsman:SelectorSet>
</s:Header>
<s:Body/>
</s:Envelope>`;
}
Loading

0 comments on commit f9d4387

Please sign in to comment.