How to inject ConfigMgr Application-Policies

In the old SMS Client Center, I had an option to inject Advertisements (Packages) on remote Clients but I never figured out how to inject Applications... But thanks to the Christmas/New-Year break, I had some time to analyze the application policy flow in Configuration Manager Current Branch (should be the same for ConfigMgr 2012).

And the final result: it's possible !! and I will soon extend Client Center with a UI to remote assign applications from the ApplicationCatalog...

Overview

The following diagram summarizes the required steps:

1) GetDeviceId

The first thing we need is the ClientID and SignedClientID from the WMI Class ROOT\ccm\ClientSdk:CCM_SoftwareCatalogUtilities with the Method GetDeviceID.
([wmiclass]'ROOT\ccm\ClientSdk:CCM_SoftwareCatalogUtilities').GetDeviceId()

SDK Documentation: GetDeviceId Method in Class CCM_SoftwareCatalogUtilities

The result will look like:

ClientId = "1.0,GUID:5c5313c2-6947-4481-bdd6-cb05e7191e2a,S-1-5-21-981701085-1898020538-528710339-1105,2017-01-04T13:37:31Z,32780";
SignedClientId = "AA925F986C80724C......";
ReturnValue = 0;

As you can see, the ClientId contains the GUID of the ConfigMgr Agent (Machine), the SID of the User that is calling the method (the Admin User, as we trigger the command from a remote AdminPC) and a TimeStamp.
The SignedClientId seems to be the signed or encrypted version of the ClientId.

2) Get Policy Body and Signature

The ApplicationCatalog contains a SOAP Web-Service where we can call the function InstallApplication to request the policy body and signature for a specific application.
The URL of the Web-Service is http://<CatalogServer>/CMApplicationCatalog/applicationviewservice.asmx or you can call the WMI method GetPortalUrlValue to get the URL from the client:
([wmiclass]'ROOT\ccm\ClientSdk:CCM_SoftwareCatalogUtilities').GetPortalUrlValue()

The service requires an HTTP POST request and the Headers must contain SOAPAction = "http://schemas.microsoft.com/5.0.0.0/ConfigurationManager/SoftwareCatalog/Website/InstallApplication" to trigger the InstallApplication function.

As input parameter, we need an XML envelope like in the following example:

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
	<s:Body>
		<InstallApplication xmlns="http://schemas.microsoft.com/5.0.0.0/ConfigurationManager/SoftwareCatalog/Website" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
			<applicationID>ScopeId_1831662F-9DA8-44D4-9EFD-637CC6E3DFE2/Application_b62a8774-9f1e-474d-809f-d6616ea0de00</applicationID>
			<deviceID>1.0,GUID:5c5313c2-6947-4481-bdd6-cb05e7191e2a,S-1-5-21-981701085-1898020538-528710339-1105,2017-01-04T12:05:54Z,32780,AA925F986C80724C......";</deviceID>
			<reserved/>
		</InstallApplication>
	</s:Body>
</s:Envelope>

It contains the ApplicationID which is the ID of the application we want to install and the DeviceID wich is a coma separated combination of the ClientID and the SignedClientID (from step 1).

The Web-Server will respond an XML with the BodySignature and a Base64 Encoded Policy Body as PolicyAssignmentsDocument. To decode the Base64-Body, you can use the following PowerShell example:
$Body = [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String($Body64))

Note: The call to the Web-Service is made from the AdminPC as we have to authenticate on the ApplicationCatalog. Calling the Web-Service from the remote Computer will end up in a Kerberos Double Hop issue.

3) ApplyPolicyEx

The SDK Documentation for ApplyPolicyEx Method in Class CCM_SoftwareCatalogUtilities is not really detailled, but that's exactly the method we have to call to inject our policy body and BodySignature (from step2)...
([wmiclass]'ROOT\ccm\ClientSdk:CCM_SoftwareCatalogUtilities').ApplyPolicyEx($Body, $BodySignature, "LOCAL")
The last parameter BodySource defines the source of the policy... It can be "LOCAL" or "SMS:<SiteCode>"...

As result we get an Id that contains the SID ,ApplicationID, and some more attributes:

User;S-1-5-21-981701085-1898020538-528710339-1105;CCM_Policy_Policy5.PolicyID="{CF701EEF-979C-412A-849D-0738EA62A972}",PolicyVersion="3.00",PolicySource="LOCAL";CCM_Policy_Policy5.PolicyID="Windows/All_Windows_Client_Server/VI/VS/L",PolicyVersion="1.00",PolicySource="LOCAL";CCM_Policy_Policy5.PolicyID="ScopeId_1831662F-9DA8-44D4-9EFD-637CC6E3DFE2/Application_38392376-284a-4f3e-99ec-e06ff0869262/CA",PolicyVersion="3.00",PolicySource="LOCAL";CCM_Policy_Policy5.PolicyID="ScopeId_1831662F-9DA8-44D4-9EFD-637CC6E3DFE2/RequiredApplication_38392376-284a-4f3e-99ec-e06ff0869262/VI/VS/L",PolicyVersion="2.00",PolicySource="LOCAL"

For troubleshooting, look at the PolicySDK.log in Windows\CCM\Logs on the target device. It will contain a detailed status if something goes wrong during ApplyPolicyEx...

Note: It's important that the same User-ID is used for all three steps! You cannot inject a policy for "another" user.

4) Trigger an Install

It may take a moment until the policy is fully loaded, but you will be able to see the application in Client Center after a few seconds.
Or you can list all available applications with the following PowerShell command:
get-wmiobject -query "SELECT * FROM CCM_Application" -namespace "ROOT\ccm\ClientSDK"

By using the Install Method in Class CCM_Application, you can trigger an installation of the application:
([wmiclass]'ROOT\ccm\ClientSdk:CCM_Application').Install('ScopeId_1831662F-9DA8-44D4-9EFD-637CC6E3DFE2/Application_38392376-284a-4f3e-99ec-e06ff0869262', 6, $False, 0, 'Normal', $False)

5)Final Note

I will first integrate this process in the SCCM Client Center Automation Library which is licensed unter GNU Library General Public License (LGPL).

All this is based on reverse engineering as the SDK is not very detailed. So no support and warranty on any side effects...