There are plenty of articles out there on the basics of using API’s with PowerShell’s great New-WebServiceProxy cmdlet that will get you started with some basic API’s, but far less posts that actually explore how to get past the simplest of queries. Once I’d gotten past the basics of connecting and requesting some data I found the information dried up pretty quickly and typically just short of achieving what I’d like to do with the API’s most of the time so after a lot of banging my head against the wall, I figured I’d post some useful findings.
Two Different Authentication Scenarios
Authentication will take on a number of forms depending on your target server. If you’re lucky, you’ll be able to use the native functionality:
$MyCredentials = Get-Credential $myURL = "https://myapi.myapiserver.com" $myAPI = New-WebServiceProxy -Uri $MyURL -Credential $MyCredentials
However, what about authentication that accepts the username and password as parameters – well that’s pretty simple – we can just pass our username and password to the API service that does the login – e.g.
$myURL = "https://myapi.myapiserver.com" $myAPI = New-WebServiceProxy -Uri $MyURL $token = $MyAPI.Login($credsUserName,$credsPassword)
and store the resulting API token for future authentication – simple!
OK, you think, so I can just do:
$myURL = "https://myapi.myapiserver.com" $MyCredentials = Get-Credential $myAPI = New-WebServiceProxy -Uri $MyURL $token = $MyAPI.Login($MyCredentials.Username,$MyCredentials.Password)
Well, maybe not – you’ll probably find you’ll get a login error and it’ll suggest you’ve simply got bad credentials. Well, not exactly- it’s because it’s passing the Password as a Secure String – you’ll need to pass this as a normal value the API can process:
$myURL = "https://myapi.myapiserver.com" $MyCredentials = Get-Credential $myAPI = New-WebServiceProxy -Uri $MyURL $token = $MyAPI.Login($MyCredentials.Username,$MyCredentials.GetNetworkCredential().password)
So now you’ve got two different authentication methods up your sleeve. Let’s move on to something more interesting…
Sending data with AutogeneratedTypes
OK, the next common scenario I’ve seen people be caught out by is trying to submit data to the API. If you do a get command on most API’s you’ll get back a nice array of objects of an autogenerated type based on the WebService Proxy. PowerShell is trying to be helpful with these types but it’s not immediately obvious how to leverage this.
For the rest of this example I’m going to use an API I’ve being working with a lot recently- the Tripwire Configuration Compliance Manager (CCM) API – I’m hoping the examples here make sense, but feel free to add a comment if not!
So I’ve connected to my API ok and I’ve got a token – how do I get some data out of the API? Well, if I want to request the Network Profiles I can run the GetNetworkProfiles call. But how do I know this exists in the API or what calls are useful? Well, I could’ve browsed the API documentation for 1, or visited the webservice API URI which would show the calls. But my preferred method (since I’m already in PowerShell) is to use the auto-complete in PowerShell to tell me the API methods available for me – the PowerShell ISE will nicely list any methods available to you. So, if I have
# ... # Set up API WebService Proxy $CCMservice = New-WebServiceProxy -Uri $uri # Get a token to use going forward... $token = $CCMservice.Login($credsUserName,$credsPassword.GetNetworkCredential().password)
And then enter $ccmservice. I can tab complete my way through the methods. That means I can do:
# ... # Set up API WebService Proxy $CCMservice = New-WebServiceProxy -Uri $uri # Get a token to use going forward... $token = $CCMservice.Login($credsUserName,$credsPassword.GetNetworkCredential().password) # Get a list of network profiles from CCM $CCMprofiles = $CCMservice.GetNetworkProfiles($token)
to store a list of profiles in $ccmprofiles- here’s the neat list of Network Profiles:
PROMPT# $CCMprofiles = $CCMservice.GetNetworkProfiles($token) PROMPT# $CCMprofiles ScanState : Stopped InsertActive : True InsertPassive : False DefaultPingIntervalInMinutes : -1 IsPendingDelete : False ProfileName : NetworkProfileA ScanEngineID : 7 VulnScanProfileID : -1 IsOpenProfile : False IsPassiveOnly : False HasActiveTask : True NetworkProfileGroupId : 1 ID : 6 IsPersisted : True IsDirty : False ScanState : Stopped InsertActive : True InsertPassive : False DefaultPingIntervalInMinutes : -1 IsPendingDelete : False ProfileName : NetworkProfileB ScanEngineID : 5 VulnScanProfileID : -1 IsOpenProfile : False IsPassiveOnly : False HasActiveTask : True NetworkProfileGroupId : 1 ID : 7 IsPersisted : True IsDirty : False
Good start. I can already do useful things with this like $ccmprofiles.count to see how many network profiles I retrieved, list all the profile names with $CCMprofiles.Profilename, or use a where-object filter to find a list of profiles where scanning is stopped: $CCMprofiles | Where-Object {$_.scanstate -eq “Stopped”}
I can also find out a bit more about what PowerShell has created – if I do
$CCMprofiles.GetType()
I see that I’ve got a custom object type of NetworkProfile[]
So I can probably be pretty certain that if I want to create a new network profile I’ll need to send a NetworkProfile object to the API- how do I create one of those? Well, let’s start by exploring what PowerShell’s created when I connect to the API. I can run:
$CCMservice.GetType().Assembly.GetExportedTypes()
To get a list of all the autogenerated types from the API – this handily includes the NetworkProfile type- let’s filter that big list of functions down to just the one named after the object we retrieved earlier:
$CCMservice.GetType().Assembly.GetExportedTypes() | where {$_.name -eq 'NetworkProfile'} | select -first 1
What we’re getting back here is an object that the API knows about as a network profile. Now we know the object type exists, we can create a new object based on this template with right?
$NewProfile= New-Object “NetworkProfile”
No… Because we need to use the full name – NetworkProfile is just a nice, friendly short name we got back. But we haven’t see a fullname anywhere, right? Well, that’s because we were only looking at some attributes of the ExportedTypes earlier- if we re-run the GetExportedTypes command but this time with a Format-List we’ll see a whole heap more:
PROMPT# $CCMservice.GetType().Assembly.GetExportedTypes() | where {$_.name -eq 'NetworkProfile'} | select -first 1 | fl Module : mpc1vvfi.dll Assembly : mpc1vvfi, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null TypeHandle : System.RuntimeTypeHandle DeclaringMethod : BaseType : Microsoft.PowerShell.Commands.NewWebserviceProxy.A utogeneratedTypes.WebServiceProxy1rcleWeb_WebClien tAPI_asmx_wsdl.NetworkProfileAuto UnderlyingSystemType : Microsoft.PowerShell.Commands.NewWebserviceProxy.A utogeneratedTypes.WebServiceProxy1rcleWeb_WebClien tAPI_asmx_wsdl.NetworkProfile FullName : Microsoft.PowerShell.Commands.NewWebserviceProxy.A utogeneratedTypes.WebServiceProxy1rcleWeb_WebClien tAPI_asmx_wsdl.NetworkProfile AssemblyQualifiedName : Microsoft.PowerShell.Commands.NewWebserviceProxy.A utogeneratedTypes.WebServiceProxy1rcleWeb_WebClien tAPI_asmx_wsdl.NetworkProfile, mpc1vvfi, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null Namespace : Microsoft.PowerShell.Commands.NewWebserviceProxy.A utogeneratedTypes.WebServiceProxy1rcleWeb_WebClien tAPI_asmx_wsdl GUID : 00416640-ec3e-3330-9344-3b9547e2c1d3 IsEnum : False GenericParameterAttributes : IsSecurityCritical : True IsSecuritySafeCritical : False IsSecurityTransparent : False IsGenericTypeDefinition : False IsGenericParameter : False GenericParameterPosition : IsGenericType : False IsConstructedGenericType : False ContainsGenericParameters : False StructLayoutAttribute : System.Runtime.InteropServices.StructLayoutAttribu te Name : NetworkProfile MemberType : TypeInfo DeclaringType : ReflectedType : MetadataToken : 33554520 GenericTypeParameters : {} DeclaredConstructors : {Void .ctor()} DeclaredEvents : {} DeclaredFields : {scanStateField, insertActiveField, insertPassiveField, defaultPingIntervalInMinutesField} DeclaredMembers : {Microsoft.PowerShell.Commands.NewWebserviceProxy. AutogeneratedTypes.WebServiceProxy1rcleWeb_WebClie ntAPI_asmx_wsdl.ScanningState get_ScanState(), Void set_ScanState(Microsoft.PowerShell.Commands.N ewWebserviceProxy.AutogeneratedTypes.WebServicePro xy1rcleWeb_WebClientAPI_asmx_wsdl.ScanningState), Boolean get_InsertActive(), Void set_InsertActive(Boolean)...} DeclaredMethods : {Microsoft.PowerShell.Commands.NewWebserviceProxy. AutogeneratedTypes.WebServiceProxy1rcleWeb_WebClie ntAPI_asmx_wsdl.ScanningState get_ScanState(), Void set_ScanState(Microsoft.PowerShell.Commands.N ewWebserviceProxy.AutogeneratedTypes.WebServicePro xy1rcleWeb_WebClientAPI_asmx_wsdl.ScanningState), Boolean get_InsertActive(), Void set_InsertActive(Boolean)...} DeclaredNestedTypes : {} DeclaredProperties : {Microsoft.PowerShell.Commands.NewWebserviceProxy. AutogeneratedTypes.WebServiceProxy1rcleWeb_WebClie ntAPI_asmx_wsdl.ScanningState ScanState, Boolean InsertActive, Boolean InsertPassive, Int32 DefaultPingIntervalInMinutes} ImplementedInterfaces : {} TypeInitializer :
There’s actually more – but for now let’s focus on what we need to do to create an object of the type we want – to do this we’ll need the FULL NAME. Great – that’s simply (!):
Microsoft.PowerShell.Commands.NewWebserviceProxy.AutogeneratedTypes.WebServiceProxy1rcleWeb_WebClientAPI_asmx_wsdl.NetworkProfile
Catchy? Well you can make you’re life easier with the commands we noted earlier-
$myNewNetworkProfileObj = $CCMservice.GetType().Assembly.GetExportedTypes() | where {$_.name -eq 'NetworkProfile'} | select -first 1 | select fullname $myNewNetworkProfileObj = $myNewNetworkProfile.FullName
Will retrieve the exported type with the name in our where clause in the first line and store it in the myNewNetworkProfile- since we only need the full name for our purposes though, in the second line I store the Fullname only (I could equally address this in future just by using the $myNewNetworkProfile.FullName but I’m lazy and it makes it harder if I later want to print this attribute using Write-host etc!).
Cool – so let’s create our new network profile we want to add:
PROMPT# $ProfileToAdd = New-Object $myNewNetworkProfileObj PROMPT# $ProfileToAdd ScanState : Running InsertActive : True InsertPassive : False DefaultPingIntervalInMinutes : 0 IsPendingDelete : False ProfileName : ScanEngineID : 0 VulnScanProfileID : 0 IsOpenProfile : False IsPassiveOnly : False HasActiveTask : False NetworkProfileGroupId : 0 ID : 0 IsPersisted : False IsDirty : False
Cool – I can now set things like the profile name by simply doing
$ProfileToAdd.ProfileName = "My New Network Profile!"
A good start but we’re not quite ready to send this to the API. Next time we can explore why not and how to actually post the data but that’s us for now!