The ConfigMgr AdminService

Update 12/04/2018: Check out the latest info on the SMS Provider API.

I posted last week about the OData connector that is included in ConfigMgr Technical Preview 1810. I did some more digging this past weekend and made a few more discoveries. Additionally, I ran into some issues during testing and was able to get in touch with some of the team working on the new AdminService and they gave me some great insight on my findings. Here’s what I’ve got so far:

RESTful API’s use Routes and Controllers to build a framework for requests based on parts of the URL, the type of request (Get, Put, Post, Delete), headers and request body. The OData standard is a for RESTful API’s and it is what the AdminService is built around. In the previous post, I mentioned that the service was v2. After working with it, I found that it was the WMI Route for AdminService and can be accessed by https://localhost/AdminService/v2/ which will become https://localhost/AdminService/wmi/ in the production release. This is the base URL for the WMI route and will produce a listing of all of the avilable entities. In this case, the entities WMI class names.

In addition the WMI route, there’s another route on https://localhost/AdminService/v1/ which lists 4 categories. From what I can tell, this route is used for a limited set of admin tasks as the moment. I have been able to run scripts on specific collections as well as Approve and Deny user application requests using this route and the correct controller/action/function combination. Additionally, this route has been configured to output OData metadata information which is critical for enabling Power BI (or other OData consumer) to properly render the data.

The product team confirmed the that v2 route isn’t fully built out at the moment, so the OData metadata can’t be rendered with the current iteration of AdminSerivice. Additionally, you will need to add your account to ConfigMgr as an admin directly (not in a group) since the RBAC functionality is still being developed so permissions won’t process correctly yet.

Sample Queries

Here are some of the query options I’ve played with. I’m using Postman for my testing, but you can use PowerShell or any browser plug-in that handles API testing. EVERYTHING IS CASE SENSETIVE and Trailing / matter!



Also Note, I’m using http in my lab, but the default is https. Double check what you are using if you have issues getting this working. See my first post for more info.

#Calling the base URL to list all of the contoller names. #Each controller can be added to the URL to entities. #Get URL http://localhost/AdminService/v1/ #Results { "@odata.context": "http://localhost/AdminService/v1/$metadata", "value": [ { "name": "DistributionPointGroups", "kind": "EntitySet", "url": "DistributionPointGroups" }, { "name": "Collections", "kind": "EntitySet", "url": "Collections" }, { "name": "UserApplicationRequest", "kind": "EntitySet", "url": "UserApplicationRequest" }, { "name": "Reports", "kind": "EntitySet", "url": "Reports" }, { "name": "HubItems", "kind": "EntitySet", "url": "HubItems" } ] }

## Adding the $metadata parameter to the URL causes this ## OData metadata document to be generated. #Get URL http://localhost/AdminService/v1/$metadata #Result <?xml version="1.0" encoding="utf-8"?> <edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx"> <edmx:DataServices> <Schema Namespace="Microsoft.ConfigurationManager.ObjectLibrary" xmlns="http://docs.oasis-open.org/odata/ns/edm"> <EntityType Name="SMS_DistributionPointGroup"> <Key> <PropertyRef Name="GroupID" /> </Key> <Property Name="GroupID" Type="Edm.String" Nullable="false" /> <Property Name="Name" Type="Edm.String" /> <Property Name="Description" Type="Edm.String" /> <Property Name="CreatedBy" Type="Edm.String" /> <Property Name="CreatedOn" Type="Edm.DateTimeOffset" Nullable="false" /> <Property Name="ModifiedBy" Type="Edm.String" /> <Property Name="ModifiedOn" Type="Edm.DateTimeOffset" Nullable="false" /> <Property Name="SourceSite" Type="Edm.String" /> <Property Name="MemberCount" Type="Edm.Int32" Nullable="false" /> <Property Name="CollectionCount" Type="Edm.Int32" Nullable="false" /> <Property Name="ContentCount" Type="Edm.Int32" Nullable="false" /> <Property Name="OutOfSyncContentCount" Type="Edm.Int32" Nullable="false" /> <Property Name="HasMember" Type="Edm.Boolean" /> <Property Name="HasRelationship" Type="Edm.Boolean" /> <Property Name="ContentInSync" Type="Edm.Boolean" /> </EntityType> <EntityType Name="Collection"> <Key> <PropertyRef Name="SiteID" /> </Key> <Property Name="CollectionID" Type="Edm.Int32" Nullable="false" /> <Property Name="SiteID" Type="Edm.String" Nullable="false" /> <Property Name="CollectionName" Type="Edm.String" /> <Property Name="Flags" Type="Edm.Int32" Nullable="false" /> <Property Name="ResultTableName" Type="Edm.String" /> <Property Name="CollectionComment" Type="Edm.String" /> <Property Name="Schedule" Type="Edm.String" /> <Property Name="SourceLocaleID" Type="Edm.Int32" /> <Property Name="LastChangeTime" Type="Edm.DateTimeOffset" /> <Property Name="LastRefreshRequest" Type="Edm.DateTimeOffset" Nullable="false" /> <Property Name="CollectionType" Type="Edm.Int32" Nullable="false" /> <Property Name="LimitToCollectionID" Type="Edm.String" /> <Property Name="IsReferenceCollection" Type="Edm.Int32" Nullable="false" /> <Property Name="BeginDate" Type="Edm.DateTimeOffset" Nullable="false" /> <Property Name="EvaluationStartTime" Type="Edm.DateTimeOffset" /> <Property Name="LastRefreshTime" Type="Edm.DateTimeOffset" /> <Property Name="LastIncrementalRefreshTime" Type="Edm.DateTimeOffset" /> <Property Name="LastMemberChangeTime" Type="Edm.DateTimeOffset" /> <Property Name="CurrentStatus" Type="Edm.Int32" Nullable="false" /> <Property Name="CurrentStatusTime" Type="Edm.DateTimeOffset" Nullable="false" /> <Property Name="LimitToCollectionName" Type="Edm.String" /> <Property Name="ISVData" Type="Edm.Binary" /> <Property Name="ISVString" Type="Edm.String" /> </EntityType> <EntityType Name="UserApplicationRequest"> <Key> <PropertyRef Name="RequestID" /> </Key> <Property Name="RequestID" Type="Edm.Int32" Nullable="false" /> </EntityType> <EntityType Name="Report"> <Key> <PropertyRef Name="Name" /> </Key> <Property Name="Name" Type="Edm.String" Nullable="false" /> <Property Name="RDL" Type="Edm.String" /> <Property Name="Hash" Type="Edm.String" /> <Property Name="HashAlgorithm" Type="Edm.String" /> <Property Name="DateCreated" Type="Edm.DateTimeOffset" Nullable="false" /> <Property Name="DateLastModified" Type="Edm.DateTimeOffset" Nullable="false" /> <Property Name="CreatedBy" Type="Edm.String" /> <Property Name="LastModifiedBy" Type="Edm.String" /> </EntityType> <EntityType Name="HubItems"> <Key> <PropertyRef Name="HubId" /> </Key> <Property Name="HubId" Type="Edm.String" Nullable="false" /> <Property Name="HubContentId" Type="Edm.String" /> <Property Name="OnPremId" Type="Edm.String" /> <Property Name="ItemType" Type="Edm.Int32" Nullable="false" /> <Property Name="DateCreated" Type="Edm.DateTimeOffset" Nullable="false" /> <Property Name="DateLastModified" Type="Edm.DateTimeOffset" /> <Property Name="CreatedBy" Type="Edm.String" /> <Property Name="LastModifiedBy" Type="Edm.String" /> </EntityType> </Schema> <Schema Namespace="AdminService" xmlns="http://docs.oasis-open.org/odata/ns/edm"> <Action Name="RunScript" IsBound="true"> <Parameter Name="bindingParameter" Type="Microsoft.ConfigurationManager.ObjectLibrary.Collection" /> <Parameter Name="ScriptGuid" Type="Edm.String" Unicode="false" /> </Action> <Function Name="ApproveRequest" IsBound="true"> <Parameter Name="bindingParameter" Type="Collection(Microsoft.ConfigurationManager.ObjectLibrary.UserApplicationRequest)" /> <Parameter Name="Guid" Type="Edm.String" Unicode="false" /> <ReturnType Type="Edm.Int32" Nullable="false" /> </Function> <Function Name="DenyRequest" IsBound="true"> <Parameter Name="bindingParameter" Type="Collection(Microsoft.ConfigurationManager.ObjectLibrary.UserApplicationRequest)" /> <Parameter Name="Guid" Type="Edm.String" Unicode="false" /> <ReturnType Type="Edm.Int32" Nullable="false" /> </Function> <EntityContainer Name="Container"> <EntitySet Name="DistributionPointGroups" EntityType="Microsoft.ConfigurationManager.ObjectLibrary.SMS_DistributionPointGroup" /> <EntitySet Name="Collections" EntityType="Microsoft.ConfigurationManager.ObjectLibrary.Collection" /> <EntitySet Name="UserApplicationRequest" EntityType="Microsoft.ConfigurationManager.ObjectLibrary.UserApplicationRequest" /> <EntitySet Name="Reports" EntityType="Microsoft.ConfigurationManager.ObjectLibrary.Report" /> <EntitySet Name="HubItems" EntityType="Microsoft.ConfigurationManager.ObjectLibrary.HubItems" /> </EntityContainer> </Schema> </edmx:DataServices> </edmx:Edmx>

# Calling v2 with $metadata doesn't return data in this verion of AdminSevice # but it will in Production #Get URL http://localhost/AdminService/v2/$metadata # Future release URL http://localhost/AdminService/wmi/$metadata #Results EMPTY

# Take any WMI class and remove the SMS_ prefix (SMS_Collection = Collection) # to create the Controller Name for the v2/wmi route. #Get URL http://localhost/AdminService/v2/Collection #Results { "@odata.context": "http://localhost/AdminService/v2/Collection", "value": [ { "CollectionID": "SMS00001", "CollectionType": 2, "CollectionVariablesCount": 0, "Comment": "All Systems", "CurrentStatus": 1, "HasProvisionedMember": true, "IncludeExcludeCollectionsCount": 0, "IsBuiltIn": true, "IsReferenceCollection": true, "ISVString": null, "LastChangeTime": "10/15/2018 1:14:27 AM +00:00", "LastMemberChangeTime": "10/22/2018 7:00:13 AM +00:00", "LastRefreshTime": "10/22/2018 2:00:14 PM +00:00", "LimitToCollectionID": null, "LimitToCollectionName": null, "LocalMemberCount": 4, "MemberClassName": "SMS_CM_RES_COLL_SMS00001", "MemberCount": 4, "MonitoringFlags": 0, "Name": "All Systems", "ObjectPath": "/", "PowerConfigsCount": 0, "RefreshType": 4, "ServiceWindowsCount": 0, "UseCluster": null }, { "CollectionID": "SMS00002", "CollectionType": 1, "CollectionVariablesCount": 0, "Comment": "All Users", "CurrentStatus": 1, "HasProvisionedMember": false, "IncludeExcludeCollectionsCount": 0, "IsBuiltIn": true, "IsReferenceCollection": false, "ISVString": null, "LastChangeTime": "10/15/2018 1:14:32 AM +00:00", "LastMemberChangeTime": "10/22/2018 3:37:34 AM +00:00", "LastRefreshTime": "10/22/2018 2:00:20 PM +00:00", "LimitToCollectionID": "SMS00004", "LimitToCollectionName": "All Users and User Groups", "LocalMemberCount": 22, "MemberClassName": "SMS_CM_RES_COLL_SMS00002", "MemberCount": 22, "MonitoringFlags": 0, "Name": "All Users", "ObjectPath": "/", "PowerConfigsCount": 0, "RefreshType": 4, "ServiceWindowsCount": 0, "UseCluster": null }, --REMOVED Several Entries to save space...-- { "CollectionID": "TST00014", "CollectionType": 2, "CollectionVariablesCount": 0, "Comment": "", "CurrentStatus": 1, "HasProvisionedMember": true, "IncludeExcludeCollectionsCount": 0, "IsBuiltIn": false, "IsReferenceCollection": false, "ISVString": "", "LastChangeTime": "10/22/2018 6:43:21 AM +00:00", "LastMemberChangeTime": "10/22/2018 7:00:15 AM +00:00", "LastRefreshTime": "10/23/2018 4:37:05 AM +00:00", "LimitToCollectionID": "SMS00001", "LimitToCollectionName": "All Systems", "LocalMemberCount": 2, "MemberClassName": "SMS_CM_RES_COLL_TST00014", "MemberCount": 2, "MonitoringFlags": 0, "Name": "Test Collection", "ObjectPath": "/", "PowerConfigsCount": 0, "RefreshType": 1, "ServiceWindowsCount": 0, "UseCluster": null }, ] }



http://localhost/AdminService/v2/Collection returns the same as Select * FROM SMS_Collection



# You can pass in the item's ID to return just a single # instance of the class #Get URL http://localhost/AdminService/v2/Collection('TST00014') #Results { "@odata.context": "http://localhost/AdminService/v2/Collection('TST00014')", "value": [ { "CollectionID": "TST00014", "CollectionType": 2, "CollectionVariablesCount": 0, "Comment": "", "CurrentStatus": 1, "HasProvisionedMember": true, "IncludeExcludeCollectionsCount": 0, "IsBuiltIn": false, "IsReferenceCollection": false, "ISVString": "", "LastChangeTime": "10/22/2018 6:43:21 AM +00:00", "LastMemberChangeTime": "10/22/2018 7:00:15 AM +00:00", "LastRefreshTime": "10/23/2018 4:37:05 AM +00:00", "LimitToCollectionID": "SMS00001", "LimitToCollectionName": "All Systems", "LocalMemberCount": 2, "MemberClassName": "SMS_CM_RES_COLL_TST00014", "MemberCount": 2, "MonitoringFlags": 0, "Name": "Test Collection", "ObjectPath": "/", "PowerConfigsCount": 0, "RefreshType": 1, "ServiceWindowsCount": 0, "UseCluster": null } ] }

# You can nest items together to drill into more detail. # Here we can get a specific device in a specific collection # and return the WMI COMPUTER_SYSTEM information from inventory. #Get URL http://localhost/AdminService/v2/Collection('TST00014')/Device(16777220)/COMPUTER_SYSTEM #Retults { "@odata.context": "http://localhost/AdminService/v2/Collection('TST00014')/Device(16777220)/COMPUTER_SYSTEM", "value": [ { "AdminPasswordStatus": null, "AutomaticResetBootOption": null, "AutomaticResetCapability": null, "BootOptionOnLimit": null, "BootOptionOnWatchDog": null, "BootROMSupported": null, "BootupState": null, "Caption": null, "ChassisBootupState": null, "CurrentTimeZone": 0, "DaylightInEffect": null, "Description": "AT/AT COMPATIBLE", "Domain": "tp.asd.com", "DomainRole": 1, "FrontPanelResetStatus": null, "GroupID": 1, "InfraredSupported": null, "InitialLoadInfo": null, "InstallDate": null, "KeyboardPasswordStatus": null, "LastLoadInfo": null, "Manufacturer": "Microsoft Corporation", "Model": "Virtual Machine", "Name": "ASD01", "NameFormat": null, "NetworkServerModeEnabled": null, "NumberOfProcessors": 1, "OEMLogoBitmap": null, "OEMStringArray": null, "PauseAfterReset": null, "PowerManagementCapabilities": null, "PowerManagementSupported": null, "PowerOnPasswordStatus": null, "PowerState": null, "PowerSupplyState": null, "PrimaryOwnerContact": null, "PrimaryOwnerName": null, "ResetCapability": null, "ResetCount": null, "ResetLimit": null, "ResourceID": 16777220, "RevisionID": 1, "Roles": "LM_Workstation, LM_Server, NT", "Status": "OK", "SupportContactDescription": null, "SystemStartupDelay": null, "SystemStartupOptions": null, "SystemStartupSetting": null, "SystemType": "x64-based PC", "ThermalState": null, "TimeStamp": "10/22/2018 2:04:29 AM +00:00", "TotalPhysicalMemory": null, "UserName": null, "WakeUpType": null } ] }

Run Scripts and Approve/Deny App Requests

Here’s where I think it get’s fun. I found a few actions/functions. The RunScript action requires changing to POST and creating a body object using Key:Value format. The script GUID can be found in the ConfigMgr console by adding the Scripts GUID column or running https://localhost/AdminService/Scripts/ to list all of your scripts.

#POST URL http://localhost/AdminService/v1/Collections('TST00014')/RunScript #POST Body (application/json) raw format. {"ScriptGuid":"17028DEA-BBC2-44E8-A30D-CCA1247AAE74"} #Results { "@odata.context": "http://localhost/AdminService/v1/$metadata#Edm.Int32", "value": 16777221 }

In the ConfigMgr console, look under Monitoring>Script Status and you can see that the script ran by comparing the Client Operation ID with the value from the results.

You can also approve or deny User Application requests, though you have to lookup the Email GUID from the ApplicationRequests table in SQL.

#SQL to get email GUID SELECT * FROM UserApplicationRequests #Poorly formatted SQL results Id RequestGuid UserID MachineResourceID ApplicationID ModelID CurrentState LastChanged LastChangedBy Comments Source EmailGuid 16777217 18B922AA-23A9-4F8B-AB99-A35291AEA309 2063597579 16777220 ScopeId_7931AA4A-E683-4AC8-A80D-F6AEB1AA2BFE/Application_d2b375b9-ee16-49ad-bc52-a43ea9774720 16777489 1 10/22/18 2:06 AM tp\Adam I need this 15 B3AB3209-3CB7-46FF-B1C8-54292FFE6521 #EmailGuid B3AB3209-3CB7-46FF-B1C8-54292FFE6521 #Get URL http://cmtp01.tp.asd.com/AdminService/v1/UserApplicationRequest/AdminService.ApproveRequest(Guid='B3AB3209-3CB7-46FF-B1C8-54292FFE6521') #Results OOPs, I forgot to save the output... #If you re-query the table, the EmailGuid will be NULL indicating the approval was processed.

Power BI

If you have made it this far, way to go! This is taking forever to write, so thanks for sticking with me. So, here’s how you add the OData connector to Power BI.

Open Power BI and Sign In (or don’t, I don’t care!)

Click Get Data to create a new report.



Select OData Feed from the list and Click Connect

Enter the v1 URL in the OData feed box

http://localhost/AdminService/v1/ and click OK



The initial load will fail.

Change to authentication to Windows then click Connect



In the Navigator you should see a list of tables. Select the tables to show data previews. **If you don’t see data, ensure that your account is in the ConfigMgr console as an Admin.





Click Load.

You should end up with 4 tables with data. Now do cool data things!

Summary

As you can see, there is already a ton of cool stuff in the new AdminService and it can only keep getting better. Build up a ConfigMgr 1810 Technical Preview box and give it a spin!