Skip navigation
All Places > PI Developers Club > Blog > 2017 > July



Today we release our first version of the PI Web API client libraries for .NET Framework and .NET Core. Those libraries were generated using the Swagger specification available on PI Web API 2017+.


Although PI AF SDK still remains the best choice in case performance is a must, there are a lot of scenarios where it makes sense to develop your .NET applications using with those libraries:


  • You cannot install PI AF SDK on the machine which would run your application.
  • You are developing a web application using Azure Web Apps, which doesn't allow you to remote in and install OSIsoft products.
  • Your PI System is hosted in the cloud. Therefore, it makes to expose the data through a public endpoint of PI Web API.
  • You want to create applications that are compatible with Linux and macOS.
  • You want to use the most modern technology of the market.


The first two reasons of the list above applies when you are developing custom .NET Framework apps.  The last 3 items from this list applies for .NET Core. But what is .NET Core exactly?


What is .NET Core?


According to the .NET blog, .NET Core is a cross-platform, open source, and modular .NET platform for creating modern web apps, microservices, libraries and console applications. Because it is cross-platform, you can run your .NET Core apps on Windows, Linux and macOS. Users can use the command-line tools to manage their project.


.NET Core is composed by 3 parts: .NET runtime, a set of framework libraries and a set of SDK tools. This new technology can be need as a cross-platform version of the .NET Framework.


.NET Core and PI Developer Technologies


.NET Core is not compatible with .NET Framework libraries like PI AF SDK. OSIsoft's product management and development teams have ongoing investigations into creating a PI AF SDK version compatible with .NET Core, but at this point there is no timeline for release. Although the PI AF SDK is faster than PI Web API, the performance of our RESTful web service is sufficient for many use cases.  The PI Web API team is committed to improving performance and adding features to each new release.



If you are developing a long running service or an application that retrieves large amounts of PI data, our suggestion is to develop on top of PI AF SDK and .NET Framework. If this is not the case, using this new client library for developing .NET Core apps should suffice your needs.





  • PI Web API 2017 installed within your domain using Kerberos or Basic Authentication.
  • If you are using the .NET Framework version of this library, you should have .NET Framework 4.5 installed on your machine.
  • If you are using the .NET Core version of this library, you should have .NET Core 1.1 installed on your machine.




  • Download this source code
  • Create a new folder under %PIHOME% named WebAPIClient, if it doesn't exist.
  • Create a new folder under WebAPIClient named DotNetCore, if it doesn't exist.
  • Copy the unique file from the dist folder to %PIHOME%\WebAPIClient\DotNetCore (.NET Core) or %PIHOME%\WebAPIClient\DotNetCore (.NET Framework).




.NET Framework


Create a new .NET Framework project (Console Application for instance). On the Solution Explorer, right click on Dependencies and then "Add Reference...". Click on the Browse button and navigate to the %PIHOME%\WebAPIClient\DotNet folder. Finally, add the OSIsoft.PIDevClub.PIWebApiClient.dll to your VS project.


.NET Core


Create a new .NET Core project (Console Application for instance). Open the Package Manager Console and run the following command to add this library to your .NET Core project.:


Install-Package OSIsoft.PIDevClub.PIWebApiClient -Source %PIHOME%\WebAPIClient\DotNetCore


Source Code


The Visual Studio solution that generates the final library is available on the src folder. You might want to add or edit a method and rebuild the solution in order to generate custom assemblies. The links from both GitHub repositories are below:





Once the library was added to your project, you can start writing code. The beauty of this library is that you don't need to know about writing code to make HTTP requests. The library will do that under the hood. All actions and methods from all the controllers of PI Web API 2017 are available on this client library. You just have to create the PI Web API top level object and access its properties. Each property maps a PI Web API controller which has methods which maps the actions from the RESTful web service. Therefore if you want to access the GetByPath action from the Point controller just call piwebapi.Point.GetByPath().


All classes and methods are described on here. Even interally both libraries are different, they have the same classes and methods from the user perspective.




Please check the Program.cs from the LibraryTest project from the Visual Studio solution of this repository. Below there are also code snippets written in C# for you to get started using this library:


Create an instance of the PI Web API top level object.


     PIWebApiClient client = new PIWebApiClient("", true);  

If you want to use basic authentication instead of Kerberos, set useKerberos to false and set the username and password accordingly.


Get the PI Data Archive WebId


    PIDataServer dataServer = client.DataServer.GetByPath("\\\\MARC-PI2016");


Create a new PI Point


    PIPoint newPIPoint = new PIPoint(); newPIPoint.Name = "MyNewPIPoint" newPIPoint.Descriptor = "Point created for wrapper test" newPIPoint.PointClass = "classic" newPIPoint.PointType = "Float32" ApiResponseObject response = client.dataServer.CreatePointWithHttpInfo(dataServer.webId, newPIPoint)


Get PI Points WebIds


    PIPoint point1 = client.Point.GetByPath("\\\\marc-pi2016\\sinusoid"); PIPoint point2 = client.Point.GetByPath("\\\\marc-pi2016\\sinusoidu"); PIPoint point3 = client.Point.GetByPath("\\\\marc-pi2016\\cdt158");


Get recorded values in bulk using the StreamSet/GetRecordedAdHoc


    List<string> webIds = new List<string>() { point1.WebId, point2.WebId, point3.WebId }; PIItemsStreamValues piItemsStreamValues = client.StreamSet.GetRecordedAdHoc(webIds, startTime: "*-3d", endTime: "*");


Send values in bulk using the StreamSet/UpdateValuesAdHoc


     var streamValuesItems = new PIItemsStreamValues(); var streamValue1 = new PIStreamValues(); var streamValue2 = new PIStreamValues(); var streamValue3 = new PIStreamValues(); var value1 = new PITimedValue(); var value2 = new PITimedValue(); var value3 = new PITimedValue(); var value4 = new PITimedValue(); var value5 = new PITimedValue(); var value6 = new PITimedValue(); value1.Value = 2; value1.Timestamp = "*-1d"; value2.Value = 3; value2.Timestamp = "*-2d"; value3.Value = 4; value3.Timestamp = "*-1d"; value4.Value = 5; value4.Timestamp = "*-2d"; value5.Value = 6; value5.Timestamp = "*-1d"; value6.Value = 7; value6.Timestamp = "*-2d"; streamValue1.WebId = point1.WebId; streamValue1.Items = new List<PITimedValue>(); streamValue1.Items.Add(value1); streamValue1.Items.Add(value2); streamValue2.WebId = point2.WebId; streamValue2.Items = new List<PITimedValue>(); streamValue2.Items.Add(value3); streamValue2.Items.Add(value4); streamValue3.WebId = point2.WebId; streamValue3.Items = new List<PITimedValue>(); streamValue3.Items.Add(value5); streamValue3.Items.Add(value6); ApiResponse<PIItemsItemsSubstatus> response2 = client.StreamSet.UpdateValuesAdHocWithHttpInfo(new List<PIStreamValues>() { streamValue1, streamValue2, streamValue3 });


Get element and its attributes given an AF Element path


     PIElement myElement = client.Element.GetByPath("\\\\MARC-PI2016\\CrossPlatformLab\\marc.adm"); PIItemsAttribute attributes = client.Element.GetAttributes(myElement.WebId, null, 1000, null, false);


Get current value given an AF Attribute path


     PIAttribute attribute = client.Attribute.GetByPath(string.Format("{0}|{1}", "\\\\MARC-PI2016\\CrossPlatformLab\\marc.adm", attributes.Items[0].Name)); PITimedValue value = client.Stream.GetEnd(attribute.WebId);


Get Event Frames given an AF Database path


    PIAssetDatabase db = client.AssetData.GetByPath(path); PIItemsEventFrame efs = client.AssetData.GetEventFrames(db.WebId, referencedElementNameFilter: "myElement", referencedElementTemplateName:


Final Remarks


As you could realize by looking at the example, it is pretty easy to use this client library on your apps. Please provide your feedback by posting your comments below!

This question comes up quite a bit at PI Square.  I know of at least 5 different ways to find out my AF Client version.  In a nutshell,


  1. From the registry settings
  2. From Programs and Features
  3. From the file properties in File Explorer
  4. From PI System Explorer (if also loaded on the client machine)
  5. From custom code


Let's go over each of these.


Registry Settings


Run regedit.exe

Navigate to:    Computer\HKEY_LOCAL_MACHINE\SOFTWARE\PISystem\AF Client

Alternative:     Computer\HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\PISystem\AF Client


2017-07-19 09_07_09-Registry Editor.png

2017-07-19 09_06_00-Registry Editor.png


2017-07-19 09_07_29-Registry Editor.png


Programs and Features

Trying to run this may take you to Apps and features.  You could filter the list to AF and see something like:


2017-07-19 09_11_51-Settings.png


If you really want the Version  number, look to the upper right of the window for Programs and Features:

2017-07-19 09_13_40-Settings.png


You should then have a list where you may scroll to:


2017-07-19 09_12_46-Programs and Features.png



File Properties in File Explorer


Using File Explorer, navigate to:     %PIHOME%\AF\PublicAssemblies\4.0


In my example below, %PIHOME% is "C:\Program Files (x86)\PIPC" but this could be a different drive on your machine.


2017-07-19 09_15_33-4.0.png


Right-click on the file:     OSIsoft.AFSDK.dll


Click on the Details tab along the tab bar at the top.


     2017-07-19 09_16_27-OSIsoft.AFSDK.dll Properties.png



From PI System Explorer


Open PSE

From the menu bar at the top, click on Help

Click on About PI System Explorer ...


2017-07-19 09_17_46-About PI System Explorer.png



Custom AFSDK Code


From a C# application that has a reference to OSIsoft.AFSDK.dll, you can use:


Console.WriteLine((new OSIsoft.AF.PISystems()).Version);


Or if you already have a  using OSIsoft.AF;  statement, this shorter version will do:


Console.WriteLine((new PISystems()).Version);


Or if you despise one-liners, you may try:


var clientSDK = new PISystems();

Console.WriteLine( clientSDK.Version );


For VB.NET, you would use an  Imports OSIsoft.AF  statement, and this line of code:


Console.WriteLine((New PISystems()).Version)


And finally, if you're not a developer or don't have Visual Studio, all is not lost.  You may still try using Powershell.



$clientSDK = New-Object OSIsoft.AF.PISystems

Write-Host "AF Client Version:" $clientSDK.Version


To produce output such as:


GAC    Version        Location

---    -------        --------                                                                                                                        

True   v4.0.30319     C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\OSIsoft.AFSDK\v4.0_4.0.0.0__6238be57836698e6\OSIsoft.AFSDK.dll                             AF Client Version:



There you go.  That's at least 5 different ways to get the AF Client Version number.  Are there any others that I may have missed?

A topic that I've seen posted quite a few times is "How do I get a custom symbol to change other symbols on the display?"  Without the knowledge of what's available to you, it can seem like a tricky or impossible task.  Once you know how to do it, it actually becomes quite easy. To get you started, I've written a couple of fun/useless symbols to show you the basics.  I may expand/improve the list in the future because I'm constantly learning new things about PI Vision Extensibility.


The Big Red Button


What does it do?


It deletes everything on your display when clicked.  It should go without saying but please don't use this on any production displays.

How does it work?

It uses the displayProvider to select all of the symbols in the display and subsequently loops though and deletes all selected symbols.

The code


<div id="bigredbutton" ng-click="pressed()"> DELETE </div>


window.PIVisualization = window.PIVisualization || {};
(function (PV) {
     'use strict';

     function symbolVis() { }
     symbolVis.prototype.init = function (scope, elem, displayProvider, $rootScope)
          scope.pressed = function(){

     var def = {
          typeName: 'bigredbutton',
          datasourceBehavior: PV.Extensibility.Enums.DatasourceBehaviors.Single,
          visObjectType: symbolVis,
          inject: [ 'displayProvider', '$rootScope'],
          getDefaultConfig: function(){
                    DataShape: 'Value',
                    Height: 150,
                    Width: 150


The Add DataStream Button

What does it do?


When the add button is hit, it searches the display for all symbols that support multiple data streams.  It then adds whatever tag or attribute is in the textbox as a datastream to each of the found symbols.

How does it work?

It uses the displayProvider to select all of the symbols in the display and subsequently loops though, checks if the DatasourceBehavior allows multiple data sources, pushes the new data source, and then deselects the symbol.

The code


<div id='add-data-source-container'>
     <input type='text' class='text-input' ng-model='newdatasource.text' placeholder='pi:\\servername\tagname'/>
     <button class='submit-button' ng-click="add()"> Add </button>


Note: Everything except the init function is the same as the previous symbol.  For brevity, just the init function is shown.

scope.newdatasource = {
     text: ""

scope.add = function(){
          //check if this symbol supports multiple datasources
          if(displayProvider.getRuntimeSymbolData(sym).def.datasourceBehavior == PV.Extensibility.Enums.DatasourceBehaviors.Multiple){
               //add the datasource from the textbox
          //deselect this symbol


The Chatting Symbols

What does it do?


The name of a receiver symbol (which must be of the same type) is entered into the top box along with a message in the bottom box.  Upon clicking "Send", that message is displayed in the destination's bottom box along with who sent it (in the top box).

How does it work?

This symbol is starting to show some real power.  We're modifying the receiver's scope to change its values in real time.

Before we get to the code

There is a behavior in Angular which makes reading another DOM element's scope impossible unless debugInfoEnabled is set to true.  In order for this symbol to work, you will need to edit a line in changing:




Be aware that this may cause a performance decrease.

The code


<div id='sayhi-source-container'>
     <input type='text' class='text-input receiver' ng-model='message.receiver' ng-click='receiverboxclicked()' placeholder='symbol name of receiver'/>
     <input type='text' class='text-input message' ng-model='message.text' placeholder='type message to receiver'/>
     <button class='submit-button' ng-click="sayhi()"> Send </button>


Note: Everything except the init function is the same as the the big red button symbol. For brevity, just the init function is shown.

var name =;

scope.message = {
     receiver: "",
     text: ""

scope.receiverboxclicked = function(){
     scope.message.receiver = scope.message.receiver.replace(' says:','');
     scope.message.text = '';

scope.sayhi = function(){
     var receiver = $('#'+scope.message.receiver);
     if(receiver.length > 0){
          var receiver_scope = receiver.scope();
               receiver_scope.message.receiver = name + " says:";
               receiver_scope.message.text = scope.message.text;
               alert(name + "'s receiver, " + scope.message.receiver + ", is not of type 'sayhi' ");
          alert(name + "'s receiver," +scope.message.receiver + ", does not exist");


The Disable Display Selection Switch (new)


What does it do?

It disables selection of symbols on the display (and also disables the right click menu)

How does it work?

We're modifying every symbol's behavior by modifying something common to all of the symbols - the display provider

The code

(styling for switch omitted)


<div style="color: white"> Selection </div>
<label class="switch">
     <input type="checkbox" ng-model="config.Enabled" ng-change="toggled()">
     <span class="slider round"></span>



symbolVis.prototype.init = function (scope, elem, displayProvider){
     var originalDisplayProvider = displayProvider['selectSymbol'];
     function allowClick(){
               displayProvider['selectSymbol'] = originalDisplayProvider;
               displayProvider['selectSymbol'] = function(){};

     setTimeout(function() { 
     }, 1000);

     scope.toggled = function(){
var def = {
     typeName: 'disableselection',
     datasourceBehavior: PV.Extensibility.Enums.DatasourceBehaviors.Single,
     visObjectType: symbolVis,
     iconUrl: 'Scripts/app/editor/symbols/ext/Icons/toggleswitch.png',
     inject: ['displayProvider'],
     getDefaultConfig: function(){
               DataShape: 'Value',
               Enabled: true,
               Height: 70,
               Width: 70,
               BackgroundColor: 'rgb(0,0,0)',
               TextColor: 'rgb(0,255,0)',


GitHub link: GitHub - osipmartin/Interacting-Symbols



Although it is considered an old technology, there are still many developers who use VBA to integrate their PI ProcessBook displays or Excel spreadsheets with the PI System. Since our most performant PI Developer Technology is PI AF SDK, which is a native .NET Framework, it cannot be used within the VBA environment. One option is to use PI SDK in VBA in order to communicate with the PI Data Archive. Nevertheless, if those developers want to work with elements, attributes or event frames, there wasn't any really good alternative.


Today we are releasing the first version of the PI Web API Wrapper for VBA, which is a client RESTful web service for PI Web API 2017. With this library, almost all methods available on PI Web API 2017 can be called through this library. This means you can get any element description, retrieve its attributes, search for event frames, create new PI Points, sends and get data in bulk within the VBA context.



It is always good to remember that PI Vision is the most suitable technology for any new development projects. This wrapper should be used only when PI Vision cannot be used for any reason.




  • PI Web API 2017 installed within your domain using Kerberos or Basic Authentication.
  • PI ProcessBook 2012 SP1+
  • .NET Framework 3.5




  • Download the latest release from our GitHub repository
  • Create a new folder under %PIHOME% named WebAPIClient, if it doesn't exist.
  • Create a new folder under WebAPIClient named VBA, if it doesn't exist.
  • Copy all files from the dist folder to %PIHOME%\WebAPIClient\VBA.
  • Run as Administrator the reg.bat located on %PIHOME%\WebAPIClient\VBA in order to register the PIWebApiWrapper assmebly.




Create or edit a PI ProcessBook display. Press ALT+F11 to open Visual Basic for Applications. On the menu, click on Tools --> References. Find PIWebApiWrapper on the list box of the available reference and add it to the VBA project.



Source Code


The Visual Studio solution that generates the final library is available on the src folder. You might want to add or edit a method and rebuild the solution in order to generate custom assemblies.




All classes and methods are described here on GitHub. You can also use the Object Browser from Visual Basic for Application to read the same information. Please refer to the screenshot below:





As this is a .NET library with COM objects and methods exposed, in order to be able to be consumed within the VBA environment, there are some things to have in mind, especially when comparing with C# development.

  • VBA is not compatible with async methods. Therefore, only sync methods are available in this library.
  • For each PI Web API action/method of each controller, there are two methods on this client library. One returns the response of the HTTP request itself and the other wraps the response on top of ApiResponse class, providing http information, such as status code. Please refer to the Get and GetWithHttpInfo methods on our documentation and you will realize the difference between them by comparing the method signature.
  • The Batch and Channel controllers are not exposed.
  • When working with data transfer objects (models) with an Items property (such as PIItemsElement), do not access or modify this property directly. Use CreateItemsArray(), GetItem(), SetItem() and GetItemsLength() instead.
  • For models that have the Value property, use SetValueWithString(), SetValueWithInt(), SetValueWithDouble() methods to set this property.
  • For the Api methods, all variables whose type are not a string must be defined. If a string variable is optional, define it as an empty string instead of Null.




There are two PI ProcessBook displays available on the Samples folder of this repository. In addition, please refer to the following examples to understand how to use this library:


Create an instance of the PI Web API top level object.


    Dim client As New PIWebApiClient
    Dim connectedToPIWebAPI As Boolean
    connectedToPIWebAPI = client.Connect("", True)


If you want to use basic authentication instead of Kerberos, set useKerberos to False and set the username and password accordingly. We recommend using Kerberos because it is the safest option. For basic authentication, the password needs to be hardcoded which is not recommended. If using Kerberos authentication is not an option, protect your VBA code with a password.


Get the PI Data Archive WebId


    Set dataServer = client.dataServer.GetByName(tbPIDataArchiveName.Text)


Create a new PI Point


    Dim response As ApiResponseObject
    Dim newPIPoint As New PIPoint
    newPIPoint.Name = "MyNewPIPoint"
    newPIPoint.Descriptor = "Point created for wrapper test"
    newPIPoint.PointClass = "classic"
    newPIPoint.PointType = "Float32"
    Set response = client.dataServer.CreatePointWithHttpInfo(dataServer.webId, newPIPoint)


Get PI Points WebIds


    Set point1 = client.point.GetByPath("\\" + tbPIDataArchiveName.Text + "\" + tbTagName1.Text)
    Set point2 = client.point.GetByPath("\\" + tbPIDataArchiveName.Text + "\" + tbTagName2.Text)
    Set point3 = client.point.GetByPath("\\" + tbPIDataArchiveName.Text + "\" + tbTagName3.Text)


Get recorded values in bulk using the StreamSet/GetRecordedAdHoc


    webIds = point1.webId + "," + point2.webId + "," + point3.webId
    Set compressedData = client.StreamSet.GetRecordedAdHoc(webIds, True, 1000)


Send values in bulk using the StreamSet/UpdateValuesAdHoc



    Call GetPIPoints
    Dim streamValuesItems As New PIItemsStreamValues
    Dim streamValue1 As New PIStreamValues
    Dim streamValue2 As New PIStreamValues
    Dim streamValue3 As New PIStreamValues
    Dim value1 As New PITimedValue
    Dim value2 As New PITimedValue
    Dim value3 As New PITimedValue
    Dim value4 As New PITimedValue
    Dim value5 As New PITimedValue
    Dim value6 As New PITimedValue

    streamValuesItems.CreateItemsArray (3)
    value1.SetValueWithInt (2)
    value1.Timestamp = "*-1d"
    value2.SetValueWithInt (3)
    value2.Timestamp = "*-2d"
    value3.SetValueWithInt (4)
    value3.Timestamp = "*-1d"
    value4.SetValueWithInt (5)
    value4.Timestamp = "*-2d"
    value5.SetValueWithInt (6)
    value5.Timestamp = "*-1d"
    value6.SetValueWithInt (7)
    value6.Timestamp = "*-2d"

    streamValue1.webId = point1.webId
    streamValue1.CreateItemsArray (2)
    Call streamValue1.SetItem(0, value1)
    Call streamValue1.SetItem(1, value2)
    Call streamValuesItems.SetItem(0, streamValue1)

    streamValue2.webId = point2.webId
    streamValue2.CreateItemsArray (2)
    Call streamValue2.SetItem(0, value3)
    Call streamValue2.SetItem(1, value4)
    Call streamValuesItems.SetItem(1, streamValue2)

    streamValue3.webId = point2.webId
    streamValue3.CreateItemsArray (2)
    Call streamValue3.SetItem(0, value5)
    Call streamValue3.SetItem(1, value6)
    Call streamValuesItems.SetItem(2, streamValue3)

    Dim response As ApiResponsePIItemsItemsSubstatus
    Set response = client.StreamSet.UpdateValuesAdHocWithHttpInfo(streamValuesItems)


Get AF Attribute given an AF Element path


    Set elem = client.element.GetByPath(ERD.CurrentContext(ThisDisplay))
    ElemDesc.Contents = elem.Description
    Dim attributes As PIItemsAttribute
    Set attributes = client.element.GetAttributes(elem.webId, 1000, False, False, False, 0)


Get current value given an AF Attribute path


  attributePath = ERD.CurrentContext(ThisDisplay) + "|" + AttrList.Text
    Set attr = client.attribute.GetByPath(attributePath)
    Set timedValue = client.Stream.GetEnd(attr.webId)
    AttrValue.Contents = timedValue.value


Get Event Frames given an AF database path


Set db = client.AssetDatabase.GetByPath(dbPath)
Set efs = client.AssetDatabase.GetEventFrames(db.webId, False, False, 100, True, 0, "", "*", "", elem.Name, elem.templateName, "", "", "None", "", "",



Final Remarks


This library will be updated for every new release of PI Web API in order to add the methods on this library which were added to the PI Web API release.


Please share your comments and thoughts about this new release! Will PI Web API Wrapper be useful in your upcoming projects?

Good day!!


We like to seek your interest level in an idea for our next programming hackathon. One of the most interesting concepts in the area of innovation and prototyping is Design Thinking.


In order to add more value to our hackathon and also enhance the quality of the event we are considering some mentorship and coaching opportunity on the subject during the hackathon.


The goal is to have a coach equip our hackers with the Design Thinking methodology. Such techniques will enable the participants to not only deliver better results at the hackathon but also take the knowledge back to their professional careers and become better PI professionals.


At the beginning of the hackathon we will include a presentation of 30-45 minutes to walk the participants through the process of design thinking and define the resources during the event. Throughout the event we will provide each team with a coach to consult with as well as certain level of access to the customer or someone who plays the role of a customer.


If we see a good amount of interest in the community we will take the next steps to include Design Thinking in our future hackathons!

Please fill in this two questions survey in order to let us know your interest: Design Thinking in OSIsoft Hackathons survey .

Thanks in advance for your support!

Filter Blog

By date: By tag: