Mimicking SCCM App Catalog via Powershell

A while back I was tasked with finding a way to script an application deployment that was user based. So basically find a way to do what the native CM 2012 application catalog did without using the catalog web interface. The result ended up being write a powershell that is created on the client machine and called by and hta script downloaded from the "other" store interface. Its not very pretty but it met their requirements.

I started my road using very useful information from the CM Team Blog
https://blogs.technet.microsoft.com/configmgrteam/2012/09/19/extending-the-application-catalog-in-system-center-2012-configuration-manager/ their blog is an excellent source to help you get your catalog setup so that you can utilize the functions that I'll be using in my examples.

From trial/error and debug log reading here is the basic flow of the application catalog interactions and how it works with the client.
1. Get 'AppID' of the application you want to install
2. Get 'DeviceID'
3. Call 'InstallApplication' webservice
4. Trigger policy refresh on client
5. Call 'Install' wmi method on client
6. Get 'ApplicabilityState' from client

Also of note, make sure you keep the WSDL visible as powershell has to load it each time you run the script unlike other compiled languages that you only need the WSDL visible while you are getting the methods to program with.

Overall I hope this information will help you accomplish your goal of scripting an install from the application catalog. Also remember that whatever user is running the script will need to be in the collection for the deployment of that application. The web services work just like using the web interface, if you don't see an app in the web catalog. You aren't going to be able to install it via the web service calls.

Setting up your web service call in your PS Script

$catalogurl = "http://sccmserver/CMApplicationCatalog";
$url = $catalogurl+"/ApplicationViewService.asmx?WSDL";  #<--WSDL must be made available in web.config file
$service = New-WebServiceProxy $url -UseDefaultCredential;


Get information about the application you are wanting to install
This is great for logging and usage in a dialog to end users.

$appid = #This is the ScopeID of the applicaiton for example if should look like ScopeId_21231C19-3346-4860-BA36-E8885D305E7A/Application_272b6fff-64e6-409f-a73a-2e36a999bba2
$WebServiceAppInfo = $service.GetApplicationDetails($appid, $null);
#sendl is my logging method, I used one I found from a web search.
sendl ("App Name: " + $WebServiceAppInfo.Name)
sendl ("App Version: " + $WebServiceAppInfo.AppVersion)
sendl ("App Publisher: " + $WebServiceAppInfo.Publisher)

Get DeviceID

$clientsdk = [wmiclass]'root/ccm/clientSDK:CCM_SoftwareCatalogUtilities';
$deviceidobj = $clientsdk.GetDeviceID();
$deviceid = $deviceidobj.ClientId+","+$deviceidobj.SignedClientId;

Call InstallApplication webservice

$WebServiceResults = $service.InstallApplication($appid, $deviceid, $null)
#I used the results of the webservice for logging
sendl ("IsAppModelApplication: " + $WebServiceResults.IsAppModelApplication)
sendl ("PolicyVersion: " + $WebServiceResults.PolicyVersion)
sendl ("PolicyModelName: " + $WebServiceResults.PolicyModelName)

Initiate Policy Refreshes on Clients
I'm using a com object so that we are basically emulating the clicking of the refresh in the control panel applet since calling the ClientSDK methods directly requires elevated privileges for the script and unless all your users are admins on their machines using the com object is best. While you really only need to refresh user policy, I decided that overkill is underrated and I refresh both user and machine policy. Since from what I can tell the catalog generates a hybrid user/machine policy on the server.

$conapp =  new-object -comobject "CPApplet.CPAppletMgr"
$actions = $conapp.GetClientActions()
Foreach ( $act in $actions)
{
If ( $act.ActionId -eq "{3A88A2F3-0C39-45fa-8959-81F21BF500CE}" ) { $act.PerformAction() | out-null}
If ( $act.ActionId -eq "{8EF4D77C-8A23-45c8-BEC3-630827704F51}" ) { $act.PerformAction() | out-null}
}

Check to see if policy has arrived on machine
You might want to put part of this in a count loop with a sleep between checks to allow for the machine to get the policy and allow for slowdowns in policy generation. You may also want to throw in the policy refresh from above before you hit your loop sleep.

$appCheck = "SELECT * FROM CCM_Application where ID = '"+$appid+"'"
$checkResult = get-wmiobject -query $appCheck -namespace "ROOT\ccm\ClientSDK"
if($checkResult -ne $null)
{
sendl ("Policy Found: " + $checkResult)
sendl ("Applicability State: " + $checkResult.ApplicabilityState)
sendl ("Install State: " + $checkResult.InstallState)
}

Trigger Application Install on Client
You will also want to make sure the application isn't already installed as if it is and you try to install it will throw an exception.

([wmiclass]'ROOT\ccm\ClientSdk:CCM_Application').Install($appid, $checkResult.Revision, $checkResult.IsMachineTarget, 0, 'Normal', $False)

Check for Application Applicability
This is useful if you have requirements on your applications. If you use this you may want to throw it in a loop similar to checking for policy arrival as this can sometimes take a little while to evaluate.

$checkResult = get-wmiobject -query $appCheck -namespace "ROOT\ccm\ClientSDK"
if ($checkResult.ApplicabilityState.ToLower() -ne "unknown")
{
sendl ("Applicability state found " + $checkResult.ApplicabilityState)
}
}

Checking if Application is Installed
This is useful to do before you try to run an installation or after if you want to report on installation or both. Just remember after is harder as you have to track the installation process.

$checkResult = get-wmiobject -query $appCheck -namespace "ROOT\ccm\ClientSDK"
if($checkResult -ne $null)
{
foreach ($b in $checkResult)
{
if ($b.InstallState.ToLower() -eq 'installed' -and $alreadyInstalled -eq $false)
{
$alreadyInstalled = $true
sendl "Software already installed"
}
}

Check for reboot pending
I added this into my script later as we found some applications and even sometimes the client won't install if there is a reboot pending.

$rebootPending = $false
$SoftRebootPending = ([wmiclass]'root/ccm/clientSDK:CCM_ClientUtilities').DetermineIfRebootPending().RebootPending
sendl ("SCCM Soft Reboot Pending: " + $SoftRebootPending)
$HardRebootPending = ([wmiclass]'root/ccm/clientSDK:CCM_ClientUtilities').DetermineIfRebootPending().IsHardRebootPending
sendl ("SCCM Hard Reboot Pending: " + $HardRebootPending)
if($SoftRebootPending -eq $true) {$rebootPending = $true}
if($HardRebootPending -eq $true) {$rebootPending = $true}
sendl ("SCCM Reboot Pending: " + $rebootPending)

Random bits of information that I found useful to log

sendl ("This Computer's Name: " + $env:computername)
sendl ("OS Version: " + [environment]::OSVersion.VersionString )
sendl ("x64 OS: " + [environment]::Is64BitOperatingSystem )
sendl ("x64 Process: " + [environment]::Is64BitProcess )
$users = get-wmiobject -query "SELECT UserSID FROM CCM_UserLogonEvents WHERE LogoffTime = NULL" -namespace "ROOT\ccm"
sendl ("User running script: " + [Environment]::UserName)
foreach ($z in $users)
{
$objSID = New-Object System.Security.Principal.SecurityIdentifier($z.UserSID)
$objUser = $objSID.Translate( [System.Security.Principal.NTAccount])
sendl ("User SCCM Client thinks is the active logged in user: " + $objUser.Value)
}


Comments

Popular posts from this blog

Stuck @ "Waiting for user logon"

Intune Hybrid - NDES Cert Issue

Triggering a software update install via Powershell