‘Why Not?’ Series – PowerShell and Steam – An Exercise in Invoke-WebRequest

During my day job I tend to get into situations where I have to make PowerShell connect to systems and applications that are atypical. A lot times, the easiest way to integrate PowerShell and these systems is the humble invoke-webrequest. It’s not a big stretch to bring that home to the ‘fun’ projects. In that vein – let’s make PowerShell talk to Steam – because ‘Why Not?’.

Steam has a pretty well documented web api, and as such, this is a pretty easy integration to get get going. I will mostly be using invoke-webrequest, since the API we are dealing is the Steam Web API. The API is mainly documented here. Another GREAT resource is this site. I won’t dig into the client side API in this post – that post is coming soon.

Let’s start by doing something simple. Let’s get a list of the api interfaces. A simple invoke-webrequest can get the data. By default, the Steam Web Api will return data in a json format, but for our demonstration I am going to get it in an XML format.

[xml]$interfaces = Invoke-WebRequest http://api.steampowered.com/ISteamWebAPIUtil/GetSupportedAPIList/v1/?format=xml -UseBasicParsing

I am using the ‘-UseBasicParsing’ switch in order to not have to initialize the IE engine and avoid performing IE initial setup. You will notice that I use the [xml] prefix to tell PowerShell to expect an XML return, and in the url I specify the xml format. Other valid formats to use in the url include json (default if nothing is specified), and vdf. If we examine the $interfaces variable, we can eventually dig down to this:

PS C:\Users\Draith> $interfaces.apilist.interfaces.interface

name                       methods
----                       -------
IGCVersion_205790          methods
IGCVersion_440             methods
IGCVersion_570             methods
IGCVersion_730             methods
IPortal2Leaderboards_620   methods
ISteamApps                 methods
ISteamBitPay               methods
ISteamDirectory            methods
ISteamEnvoy                methods
ISteamNews                 methods
ISteamPayPalPaymentsHub    methods
ISteamRemoteStorage        methods
ISteamUserAuth             methods
ISteamUserOAuth            methods
ISteamUserStats            methods
ISteamWebAPIUtil           methods
ISteamWebUserPresenceOAuth methods
ITFSystem_440              methods
IPlayerService             methods
IAccountRecoveryService    methods

Lots of interfaces here, but one immediately catches the eye – ISteamApps. How do we access that one? Well, we would need to look at the methods associated with that interface.

PS C:\Users\Draith> $interfaces.apilist.interfaces.interface|Where-Object {$_.name -eq 'ISteamApps'}|Select-Object -ExpandProperty methods

method                                                      
------                                                      
{GetAppList, GetAppList, GetServersAtAddress, UpToDateCheck}

There we go – looks like 2 GetAppList methods, and 2 others. If we were to dig a bit deeper, we would see that the GetAppList methods are version 1 and version 2. Let’s use version 2 for this example. Now that we know the Interface (ISteamApps) and the method (GetAppList), we can craft the web request as documented in the Steam Web Api.

[xml]$apps = invoke-webrequest -uri 'http://api.steampowered.com/ISteamApps/GetAppList/v0002/?format=xml' -UseBasicParsing

This is a very similar request to the first one we used to get the interfaces, only we are now supplying the interface and the method. Notice the v0002 portion – it’s the version. Again we told the request to use the XML format.

PS C:\Users\Draith> $apps.applist.apps.app

appid name                                                         
----- ----                                                         
5     Dedicated Server                                             
7     Steam Client                                                 
8     winui2                                                       
10    Counter-Strike                                               
20    Team Fortress Classic                                        
30    Day of Defeat                                                
40    Deathmatch Classic                                           
50    Half-Life: Opposing Force                                    

<and many many many more>

Wow – we just pulled back a list of every game published to Steam. I am not sure what the status has to be (Alpha, Beta, Pre-Order, etc..), but you can see the list is very extensive – almost 29000 games when I wrote this post. We stored this data in the $app variable so we only have to pull it once (really don’t want to tick off Gabe by pulling this multiple times).

Now that we have this, what good is it? Well, for a good portion of the Web Api’s, we will need to know an AppID. If we wanted to get the AppID for a specific game, we can do something like this:

PS C:\Users\Draith> $apps.applist.apps.app|Where-Object {$_.name -like '*kerbal*'}

appid  name                     
-----  ----                     
220200 Kerbal Space Program     
231410 Kerbal Space Program Demo

There we go – Kerbal Space Program (KSP) has AppID of 220200 for the main (non-demo) game. Great! Why don’t get see if we can get the latest news on KSP? Remember our $interfaces variable? One of the interfaces was ISteamNews. Looking at the methods on ISteamNews we find this:

PS C:\Users\Draith>$interfaces.apilist.interfaces.interface|Where-Object {$_.name -eq 'ISteamNews'}|Select-Object -ExpandProperty methods

method
------
{GetNewsForApp, GetNewsForApp}

Look familiar? As you can guess, there is a v0001 and v0002 version of the same method. Crafting the URL is pretty easy at this point, except when we try like we did previously, we get this:

PS C:\Users\Draith> invoke-webrequest -uri 'http://api.steampowered.com/ISteamNews/GetNewsForApp/v0002/?format=xml' -UseBasicParsing
invoke-webrequest : Bad RequestBad RequestPlease verify that all required parameters are being sent
At line:1 char:1
+ invoke-webrequest -uri 'http://api.steampowered.com/ISteamNews/GetNew ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-WebRequest], WebException
+ FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand

Apparently there is a parameter that is missing. How do we find that? Well, we have to dig in a bit:

PS C:\Users\Draith> $interfaces.apilist.interfaces.interface|Where-Object {$_.name -eq 'ISteamNews'}|Select-Object -ExpandProperty methods|Select-Object -ExpandProperty method|Where-Object {$_.version -eq 2}|Select-Object -ExpandProperty parameters|Select-Object -ExpandProperty parameter

name      type   optional description
----      ----   -------- -----------
appid     uint32 false    AppID to retrieve news for
maxlength uint32 true     Maximum length for the content to return, if this is 0 the full content is returned, if it's less then a blurb is generated to fit.
enddate   uint32 true     Retrieve posts earlier than this date      (unix epoch timestamp)
count     uint32 true     # of posts to retrieve (default 20)
feeds     string true     Comma-seperated list of feed names to return news for

There we go – a list of the parameters that the interface expects. The only mandatory one (optional = false) is the appid. Let’s try our web request again, but supply the appid this time:

[xml]$kspnews = invoke-webrequest -uri 'http://api.steampowered.com/ISteamNews/GetNewsforapp/v0002/?appid=220200&amp;format=xml' -UseBasicParsing

Notice how we have the interface set(ISteamNews) and the method (GetNewsForApp), and the version (v0002), as well as supplied the appid (220200). That leaves us with something like this:

PS C:\Users\Draith> $kspnews.appnews.newsitems.newsitem

gid : 252584970553375805
title : What No Man's Sky could learn about exploration from Kerbal Space Program
url : http://store.steampowered.com/news/externalpost/pcgamer/252584970553375805
is_external_url : true
author :
contents :
To get a jump start on No Man s Sky s space sandbox, some of our staff are playing the game on PS4 this week. Read more of our different opinions on the game as we continue to play.
My journey in No Man's Sky has so far been like going to a thri

<And much much more>

Great – what do we actually have? If we go a gettype() on $kspnews.appnews.newsitems.newsitem we find it’s an array. That makes it fairly simple to manipulate via the pipeline:

PS C:\Users\Draith> $kspnews.appnews.newsitems.newsitem|foreach{$_.title}
What No Man's Sky could learn about exploration from Kerbal Space Program
PC Gamer UK Podcast 019: Top 100 picks
Kerbal Space Program patch 1.1.3 is now available!
Kerbal Space Program Lead Dev Quits For Planets New
Kerbal Space Program lead developer calls it quits
Patch 1.1.2 is now available!
Kerbal Space Program patch 1.1.1 is now available!
Kerbal Space Program Launches Patch 1.1
Kerbal Space Program update 1.1 ???Turbo Charged??? is now available!
Midweek Madness - Kerbal Space Program, 40% Off
The Games That Got Away In 2015
Best Simulation 2015 Kerbal Space Program
Weekend Deal - Kerbal Space Program, 40% Off
The RPS Advent Calendar, Dec 13th: Kerbal Space Program
SteamController Support for KSP!
KSP 1.0.5 is Live
Kerbal Space Program update 1.0.5 adds sea landings, gives Val a face plate
We won the Golden Joystick for best indie game..
Daily Deal - Kerbal Space Program, 40% Off
Two Unity Awards! Thank you for voting

It’s a small leap to pull back the details for a particular news article. For example, if we wanted to see the details on the 1.1.2 patch, we can do this:

PS C:\Users\Draith> $kspnews.appnews.newsitems.newsitem|Where-Object{$_.title -eq 'Patch 1.1.2 is now available!'}|Select-Object -ExpandProperty contents

[img]http://i.imgur.com/plC31xC.png[/img]
Hello everyone!
We noticed a number of issues persisted through the 1.1.1 patch earlier this week. We???re releasing patch 1.1.2 to address these issues before we head off to a long overdue vacation for the next couple of weeks. Patch 1.1.2 addresses issues with the user interface and landing legs, amongst others.
Check out the full changelog on [url=http://forum.kerbalspaceprogram.com/index.php?/developerarticles.html/kerbal-space-program-patch-112-is-now-live-r191/]our forums[/url].
The Patch will start downloading through your Steam client automatically.

Pretty nifty – you can manipulate the content from there as normal html.

Getting the news is one thing, but what if you wanted to get a bit more data – something a little more exciting like the number of players actually _playing_ the game at the moment. Again – a simple invoke-webrequest can pull that – this time using the ISteamUserStats interface:

PS C:\Users\Draith> (Invoke-WebRequest -uri 'http://api.steampowered.com/ISteamUserStats/GetNumberOfCurrentPlayers/v0001/?appid=220200&format=json' -UseBasicParsing|convertfrom-json).response|Select-Object player_count

player_count
------------
        2548

There you go! We just pulled back the live player count for a steam game using PowerShell! This invoke is slightly different than the previous ones – here we are pulling the data in a json format, piping that to a ConvertFrom-Json cmdlet, and only selecting the response property. From that we pull the player_count.

One more example using invoke-webrequest – lets pull the Achievement percentage for a particular game. In this case, we can’t use KSP because that particular game doesn’t track achievements. Instead, we will use Team Fortress 2 (appid 440). Note that this interface/method uses the parameter gameid, not appid. Don’t ask why, because I have no idea.

PS C:\Users\Draith> $achievements = (Invoke-WebRequest -uri 'http://api.steampowered.com/ISteamUserStats/GetGlobalAchievementPercentagesForApp/v0001/?gameid=440&format=json' -UseBasicParsing|convertfrom-json).achievementpercentages.achievements

PS C:\Users\Draith> $achievements.achievement

name                                                       percent
----                                                       -------
TF_SCOUT_LONG_DISTANCE_RUNNER                   54.461830139160156
TF_HEAVY_DAMAGE_TAKEN                           46.819679260253906
TF_GET_CONSECUTIVEKILLS_NODEATHS                 44.32537841796875
TF_PYRO_CAMP_POSITION                           36.193519592285156
TF_KILL_NEMESIS                                 34.089653015136719

There are plenty of interfaces and interfaces you can pull – GetGlobalStatsForGame, UpToDateCheck (check to see if an app version is up to date), GetServerInfo (check Web API server status), GetWorldStatus (Specific for TF2), etc… Using the appropriate UserID, and the Steam Web API Key, you can even use ISteamUser to get Friends lists, get player profile information, find players achievements for a particular game, and more! Steam has done a great job supplying this API, and with PowerShell, you can access this data with little more than a simple invoke-webrequest.

SiteScope Logging to a SQL AlwaysOn Availability Group

SiteScope can log monitor status and response data to a couple of locations – flat files and databases, primarily.  While useful, it does fail when you attempt to send those logs to a Microsoft SQL Server Availability Group (AlwaysOn). If we use a new driver, however, we can make it work.

Versions:  SiteScope 11.32, SQL 2014, JDBC driver 4.2

The first thing to do is download the latest JDBC driver from Microsoft’s website – https://www.microsoft.com/en-us/download/details.aspx?displaylang=en&id=11774
I downloaded the sqljdbc_6.0.7507.100_enu.exe version since it was the latest at the time of this post. Download the file and open the exe. It will prompt you for an extraction location. You will only need 2 files from this extract, so a temp location is fine. Grab the latest sqljdbc file (sqljdbc42.jar in this case) and copy it to the \WEB-INF\lib directory. From the extract, you also need to grab the sqljdbc_auth.dll file and copy it to the c:\windows\system32 directory on the SiteScope server.

On the SiteScope server, remove the \WEB-INF\lib\sqljdbc.jar file (delete or move it out of the SiteScope directories entirely). Be advised that any previous database connections that use this driver can fail, so test those after removing the file and restarting SiteScope. Launch SiteScope, navigate to Preferences->Log Preferences, and enter “jdbc:sqlserver://:1433;databaseName=;IntegratedSecurity=true;MultiSubnetFailover=true” (without the quotes) as the database connection URL. Enter “com.microsoft.sqlserver.jdbc.SQLServerDriver” (without the quotes) as the database driver. Leave the username and password entries blank.

The important pieces in play here – the updated driver supports the connection property MultiSubnetFailover. That tells the connection it can fail over to another member of the AG. The entry for IntegratedSecurity=true tells the driver that the user that is running SiteScope will be making the connection. It is important that the SiteScope service is running as a user that has rights to Create and Insert on the database.

Restart the SiteScope service. It will attempt to connect to the database in the URL, create a table called SiteScopeLog, and start logging after a few monitor runs (default 20).

Bonus – you can update the table that SiteScope logs to – for example, if you want multiple SiteScopes logging to a single database but have different tables, you can have each log to a table that is unique for that SiteScope. This support article details how you can change the master.config file – specifically the CREATE and INSERT statements.

Hope this article helps. This took way to long to figure out how to do, so I hope it saves you some time.