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 available 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 AdminService. 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 SENSITIVE 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.

 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
#Calling the base URL to list all of the controller 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"
        }
    ]
}
  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
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
## 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>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# Calling v2 with $metadata doesn't return data in this version of AdminService
# but it will in Production

#Get URL
http://localhost/AdminService/v2/$metadata

# Future release URL
http://localhost/AdminService/wmi/$metadata

#Results

EMPTY
 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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# 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
`http://localhost/AdminService/v2/Collection` returns the same as `Select * FROM SMS_Collection`

 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
# 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
        }
    ]
}
 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
59
60
61
62
63
64
65
66
67
68
69
70
# 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

#Results

{
    "@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 gets 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.

A Square Dozen Image

A Square Dozen Image

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#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.

A Square Dozen Image

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#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.

  1. Open Power BI and Sign In (or don’t, I don’t care!)
  2. Click Get Data to create a new report.
  3. Select OData Feed from the list and Click Connect
    Select OData Feed from the list and Click Connect
  4. Enter the v1 URL in the OData feed box http://localhost/AdminService/v1 and click OK
    Enter the v1 URL in the OData feed box <code>http://localhost/AdminService/v1</code> and click <strong>OK</strong>
  5. The initial load will fail.
    The initial load will fail.
  6. Change to authentication to Windows then click Connect
    Change to authentication to Windows then click Connect**
    Change to authentication to Windows then click Connect
    ]
  7. 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.
    A Square Dozen Image
  8. Click Load.
    A Square Dozen Image
    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!