Turn any PowerShell script into a HTTP REST API!
C# PowerShell Roff
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
Common
Configuration
Controllers
DynamicPowerShellApi.Host
ETW
Exceptions
Jobs
Logging
Middleware
Model
Owin
Properties
ScriptRepository
Security
Tests
.gitignore
AddAccountToLogonAsService.ps1
Aperture (Web).ruleset
Aperture.ruleset
Aperture.snk
App.config
AuthorizeIfEnabledAttribute.cs
BuildNuGetPackages2.ps1
BuildNugetPackages.ps1
Constants.cs
DynamicPowerShellAPI.csproj
DynamicPowerShellAPI.sln
DynamicPowerShellAPI.sln.DotSettings
DynamicPowerShellApi.csproj
DynamicPowershellApiEvents.cs
GenericActionSelector.cs
GenericControllerSelector.cs
GetNextBuildVersion.ps1
IRunner.cs
JsonContent.cs
LICENSE
PowerShellAPI.Host.1.0.7.0-develop.nupkg
PowershellRunner.cs
README.md
RunStyleCop - Automation.proj
RunStyleCop.proj
RunspacePoolWrapper.cs
Settings.StyleCop
SolutionAssemblyInfo.cs
TeamBuild.runsettings
_EventRegisterUsersGuide.docx
packages.config

README.md

PowerShell.REST.API

Build status

Turn any PowerShell script into a HTTP REST API!

Builds

Overview

This project is a HTTP service written in C#.NET using the Microsoft OWIN libraries.

The concept is to take an existing PowerShell script, with parameters and expose it as a HTTP/REST method.

The web service configures the web methods as boot based on the configuration of the script repository.

It hosts a PowerShell runspace pool to load, run and check PowerShell scripts, capturing errors and stack traces to the logs and parsing complex PowerShell response objects back into JSON.

It also supports async jobs to be run as separate threads, with the job results to be stored on disk.

How it works

This project implements a OWIN WebAPI HTTP service with a single "generic" API controller. The API controller consults the configuration collection of the endpoint to identify which PowerShell script needs to be run for each HTTP request. It hosts a PowerShell session to import and run the script, whilst monitoring the script process for faults. It converts the response of the script to a temporary JObject and then returns the response data.

It also converts any GET or POST parameters to the web method to a parameter collection and passes them to the PowerShell process.

Running

run on the command line

DynamicPowerShellApi.Host.exe --console

run as a service

DynamicPowerShellApi.Host.exe --service

install the service

DynamicPowerShellApi.Host.exe --install-service --service-user "UserABC" --service-password "Password123"

Configuration

The main service configuration file

The file DynamicPowerShellApi.Host.exe.config is the main configuration file. It contains the setup for security, logging and the methods themselves.

<WebApiConfiguration HostAddress="http://localhost:9000">
		<Jobs JobStorePath="c:\temp\" />
		<Authentication Enabled="false" StoreName="My" StoreLocation="LocalMachine" Thumbprint="E6B6364C75ED8B6495A42D543AC728B4C2263082" Audience="http://aperture.identity/connectors" />
		<Apis>
			<WebApi Name="Example">
				<WebMethods>
					<WebMethod Name="GetMessage" AsJob="true" PowerShellPath="Example.ps1">
						<Parameters>
							<Parameter Name="message" Type="string" />
						</Parameters>
					</WebMethod>
				</WebMethods>
			</WebApi>
		</Apis>
	</WebApiConfiguration>

Configuring the HTTP listeners URI

Running on a specific IP (IPv4)

<WebApiConfiguration HostAddress="http://12.32.12.42:9000">

Running on any IP (IPv4 and IPv6)

<WebApiConfiguration HostAddress="http://+:9000">

Adding a web API

If you wanted to offer a script HTTP GET:/foo/bar, POST:/foo/baz

First, add a WebApi element with the name foo

    <Apis>
        ...
		<WebApi Name="foo">

Then for your script, bar.ps1, by convention each script should pipe the return object through ConvertTo-Json, this is because PowerShell's dynamic objects can contain circular references and cause JSON convertors to crash.

Take your named parameters, for example $message and do something with them, in this example

param ( 
	$message
	)
# go backwards
$back_message = -join $message[-1..-$message.Length]

@{ "message" = $back_message } | ConvertTo-Json -Compress

Now, add the method to the configuration file by adding an WebMethod Element

  • Name The name of the method, which matches the URL pattern /{WebApi:Name}/{WebMethod:Name}?params
  • AsJob Whether to run the script synchronously (false) or async (true)
  • PowerShellPath The script path relative to the ScriptRepository directory.

Then, add a Parameter Element to the Parameters collection for each parameter, either POST or GET.

    <Apis>
        ...
		<WebApi Name="foo">
            <WebMethods>
                <WebMethod Name="bar" AsJob="false" PowerShellPath="bar.ps1">
                    <Parameters>
                        <Parameter Name="message" Type="string" />
                    </Parameters>
                </WebMethod>
            </WebMethods>

Testing your script

Start up the API host from a console

.\DynamicPowerShellApi.Host.exe --console

Console

Using a tool like Postman you can check your script output

Example response

returns

{
  "message": "zab"
}

Authentication

By default, authentication is disabled for testing purposes. The primary authentication option is JSON Web-Token (JWT)

You can re-enable it by setting Enabled to "true" then configure the following values to enable JWT auth.

  • StoreName - The Windows certificate store name, e.g. My, Root, Trust
  • StoreLocation - The location store, e.g. LocalMachine, CurrentUser
  • Thumbprint - The thumbprint of the SSL certificate
  • Audience - The expected JWT audience for inbound tokens Help
<Authentication Enabled="false" StoreName="My" StoreLocation="LocalMachine" Thumbprint="E6B6364C75ED8B6495A42D543AC728B4C2263082" Audience="http://dimensiondata.com/auth/connectors" />

Adding another authentication option

If you want to use another authentication option, you can leverage OWIN middleware to plug and play OAuth, certificates or any of the other supported auth methods.

In DynamicPowerShellApi.Owin/Startup.cs replace the existing auth configuration with your choice, e.g. OAuth

    // If the config file specifies authentication, load up the certificates and use the JWT middleware.
    if (WebApiConfiguration.Instance.Authentication.Enabled)
    {
        appBuilder.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()
        {
            AccessTokenFormat = "eg.."
            ...
        });
       
    }

Error Handling

By default, any terminal errors in your powershell script will cause the HTTP response code to be HTTP500,

You will get the following response from the API

{
  "Message": "Error reading JObject from JsonReader. Current JsonReader item is not an object: String. Path '', line 1, position 5.",
  "Success": false,
  "LogFile": "bar130899475577107290.xml",
  "ActivityId": "bc346446-9964-4ff2-ad45-d7b13efe84b5"
}

Also, it will log the error in a Logs folder underneath the host directory.

Example error log

<?xml version="1.0" encoding="utf-8"?>
<CrashLogEntry xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <ActivityId>bc346446-9964-4ff2-ad45-d7b13efe84b5</ActivityId>
  <LogTime>2015-10-22T11:32:37.710729+11:00</LogTime>
  <RequestUrl>http://localhost:9000/api/foo/bar?message=baz</RequestUrl>
  <RequestAddress />
  <Exceptions>
    <PowerShellException>
      <ScriptName>GenericController.cs</ScriptName>
      <ErrorMessage>Error reading JObject from JsonReader. Current JsonReader item is not an object: String. Path '', line 1, position 5.</ErrorMessage>
      <LineNumber>0</LineNumber>
      <StackTrace>   at Newtonsoft.Json.Linq.JObject.Load(JsonReader reader)
   at Newtonsoft.Json.Linq.JObject.Parse(String json)
   at DynamicPowerShellApi.Controllers.GenericController.&lt;ProcessRequestAsync&gt;d__1f.MoveNext()</StackTrace>
    </PowerShellException>
  </Exceptions>
  <RequestMethod>bar</RequestMethod>
</CrashLogEntry>