Why oh why would anyone want to do this?
Recently I was building a web front-end in AngularJS and NodeJS, and desperately wanted to use native PowerShell to query Active Directory as well as leverage the get-WMIObject cmdlet to extract data from Microsoft servers in real-time. And while there are PowerShell modules for NodeJS, they are quite limited in what they can do.
So, rather than work with the limited functionality of a NodeJS plugin I decided to build my own RESTful API that would execute any command I want and return the resultant data as JSON objects.
The end result will be a dynamically customizable set of URLs that the API server can interpret, process and return data.
This script (running on server named ‘apiserver’) is going to be set to receive requests in the following format: http://apiserver:8000/commandtype/parameter1/parameter2
In the specific case of using this API to query any WMI Class on any server, the format is more accurately described as:
http://apiserver:8000/wmi/win32_class/server_name
So if I wanted to get the win32_bios WMI class data for server DC01, I would issue a GET request to:
http://apiserver:8000/wmi/win32_bios/dc01
Or if I wanted to get data from a different class, like win32_operatingsystem, it would be:
http://apiserver:8000/wmi/win32_operatingsystem/dc01
(you see how we should be able to sub-in any win32 class and server name to dynamically get the data we need)
Here is the code to make it happen. The comments should be self-explanatory (enough for you to be able to modify it to suit your needs):
WARNING – the following code should not be implemented in a production environment without careful consideration of things like:
– Code Injection
– Permissions
– Other scary stuff
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
# Create a listener on port 8000 $listener = New-Object System.Net.HttpListener $listener.Prefixes.Add('http://+:8000/') $listener.Start() 'Listening ...' # Run until you send a GET request to /end while ($true) { $context = $listener.GetContext() # Capture the details about the request $request = $context.Request # Setup a place to deliver a response $response = $context.Response # Break from loop if GET request sent to /end if ($request.Url -match '/end$') { break } else { # Split request URL to get command and options $requestvars = ([String]$request.Url).split("/"); # If a request is sent to http:// :8000/wmi if ($requestvars[3] -eq "wmi") { # Get the class name and server name from the URL and run get-WMIObject $result = get-WMIObject $requestvars[4] -computer $requestvars[5]; # Convert the returned data to JSON and set the HTTP content type to JSON $message = $result | ConvertTo-Json; $response.ContentType = 'application/json'; } else { # If no matching subdirectory/route is found generate a 404 message $message = "This is not the page you're looking for."; $response.ContentType = 'text/html' ; } # Convert the data to UTF8 bytes [byte[]]$buffer = [System.Text.Encoding]::UTF8.GetBytes($message) # Set length of response $response.ContentLength64 = $buffer.length # Write response out and close $output = $response.OutputStream $output.Write($buffer, 0, $buffer.length) $output.Close() } } #Terminate the listener $listener.Stop() |
To test this out without getting into building a full Javascript front-end, a regular web browser can be used to confirm it’s working as expected. The result should look something like this:
This is awesome. I want to try to integrated this into MDT OS Deployment.
Hi, nice write-up. Can this be used to change stuff as well using post or put options?
I believe you could – it would just be a matter of deconstructing the POSTed data.
Depending on the use-case, you could potentially just have the data as a series of “sub-folders” in the URL (like the win32_bios calls in the example).
So long as your receiving code knows how to deconstruct the long URL string http://apiserver:8000/param1/data1/data2/data3/data4… etc you could do it.
You could/should also add a conditional check in the headers for an originating IP address to limit where you can receive API calls from.
Great write-up Kamal! I posted an example on gist using your code as base with added capability of taking a json post. https://tech.zsoldier.com/2018/08/powershell-making-restful-api-endpoint.html
Nice one!
Wow, this is so cool a script like that !
Thank you for sharing.
In your post you talked about the permission, would you have any idea how to manage them ?
The code above is really just a starting point, and more of a proof of concept, than something I would use in real-life. You would honestly need a dedicated developer to write-up authentication, cookie/session handling, and well, I’m not sure if it’s worth it.
You could lock the use of the API down by using Windows Firewall (by Source IP), or maybe have it only respond on a specific NIC (not on a production network). So, there are a few non-code options.
As written, I don’t think any (decent) IT security person would agree with using the above in production.
Hi
Is it possible to access this API across the network using the system IP address.I tried it is not working
Yes, definitely. It’s likely that a network-based firewall or the Windows firewall is preventing it from working.
From the other computer, you can use the test-netconnection PowerShell cmdlet to see if communication on the specific port is getting through.